

/*-------------------------------------------------------------------------
 *
 * postmaster.c
 *	  该程序充当 POSTGRES 系统请求的清理中心。前端程序向 Postmaster 发送启动消息，Postmaster 使用消息中的信息来设置后端进程。
 *
 *	  Postmaster 还管理系统范围的操作，例如启动和关闭。Postmaster 本身并不会执行这些操作，注意 —— 它只是分叉出一个子进程在适当的时间来执行它们。它还负责在后端崩溃时重置系统。
 *
 *	  Postmaster 进程在启动时创建共享内存和信号量池，但通常不直接操作它们。特别地，它不是 PGPROC 后端数组的成员，因此不能参与锁管理操作。让 Postmaster 避免操作共享内存可以使其更简单、更可靠。Postmaster 几乎总是能够通过重置共享内存来恢复个别后端的崩溃；如果它在共享内存上做得太多，那么它就会容易和后端一起崩溃。
 *
 *	  当收到请求消息时，我们现在立即 fork()。子进程对请求执行身份验证，然后如果成功则成为后端。这允许身份验证代码以简单的单线程风格编写（与之前所需的粗糙的“穷人的多任务”代码相对）。更重要的是，它确保了在非多线程库（如 SSL 或 PAM）中的阻塞不会导致其他客户端的服务拒绝。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/postmaster/postmaster.c
 *
 * NOTES
 *
 * Initialization:
 *		Postmaster 为后端设置共享内存数据结构。
 *
 * Synchronization:
 *		Postmaster 与后端共享内存，但应避免触碰共享内存，以免在崩溃的后端搞乱锁或共享内存时变得卡住。同样，Postmaster 不应在来自前端客户端的消息上阻塞。
 *
 * Garbage Collection:
 *		Postmaster 在后端紧急退出和/或产生核心转储时进行清理。
 *
 * Error Reporting:
 *		只使用 write_stderr() 报告“互动”错误（本质上是命令行上的无效参数）。一旦启动了 Postmaster，使用 ereport()。
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <sys/wait.h>
#include <ctype.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <sys/param.h>
#include <netdb.h>
#include <limits.h>

#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif

#ifdef USE_BONJOUR
#include <dns_sd.h>
#endif

#ifdef USE_SYSTEMD
#include <systemd/sd-daemon.h>
#endif

#ifdef HAVE_PTHREAD_IS_THREADED_NP
#include <pthread.h>
#endif

#include "access/transam.h"
#include "access/xlog.h"
#include "access/xlogrecovery.h"
#include "catalog/pg_control.h"
#include "common/file_perm.h"
#include "common/ip.h"
#include "common/pg_prng.h"
#include "common/string.h"
#include "lib/ilist.h"
#include "libpq/auth.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
#include "libpq/pqsignal.h"
#include "pg_getopt.h"
#include "pgstat.h"
#include "port/pg_bswap.h"
#include "postmaster/autovacuum.h"
#include "postmaster/auxprocess.h"
#include "postmaster/bgworker_internals.h"
#include "postmaster/fork_process.h"
#include "postmaster/interrupt.h"
#include "postmaster/pgarch.h"
#include "postmaster/postmaster.h"
#include "postmaster/syslogger.h"
#include "replication/logicallauncher.h"
#include "replication/walsender.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
#include "storage/proc.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/datetime.h"
#include "utils/memutils.h"
#include "utils/pidfile.h"
#include "utils/ps_status.h"
#include "utils/queryjumble.h"
#include "utils/timeout.h"
#include "utils/timestamp.h"
#include "utils/varlena.h"

#ifdef EXEC_BACKEND
#include "storage/spin.h"
#endif


/*
 * 后端的可能类型。除了在 struct bkend 中可能的 bkend_type 值外，
 * 这些是 SignalSomeChildren() 和 CountChildren() 的可 OR 组合请求标记位。
 */
#define BACKEND_TYPE_NORMAL		0x0001	/* 正常后端 */
#define BACKEND_TYPE_AUTOVAC	0x0002	/* 自动真空工作进程 */
#define BACKEND_TYPE_WALSND		0x0004	/* walsender 进程 */
#define BACKEND_TYPE_BGWORKER	0x0008	/* bgworker 进程 */
#define BACKEND_TYPE_ALL		0x000F	/* 上述所有类型的 OR */

/*
 * 活动后端（或子进程）列表；我们实际上不知道给定的子进程是否
 * 已成为后端，或仍处于授权阶段。这主要用于跟踪我们
 * 有多少个子进程，并在必要时发送适当的信号。
 *
 * 如上面列出的后端类型所示，该列表不仅包括“正常”的客户端会话，
 * 还包括自动真空工作者、walsender 和后台工作者。
 * （请注意，在启动时，walsender 被标记为 BACKEND_TYPE_NORMAL；
 * 当注意到它们的 PMChildFlags 条目发生变化时，我们会将其重新标记为 BACKEND_TYPE_WALSND。
 * 因此，这个检查必须在任何需要区分 walsender 和正常后端的操作之前进行。）
 *
 * 此外，“dead_end” 子进程也在其中：这些子进程的启动仅为向
 * 准客户端发送友好的拒绝消息。我们必须跟踪它们，因为它们
 * 附加到共享内存，但我们知道它们永远不会成为活动后端。
 * dead_end 子进程没有分配 PMChildSlot。dead_end 子进程的 bkend_type 为 NORMAL。
 *
 * “特殊” 子进程，如启动、bgwriter 和自动真空启动器任务不在此列表中。
 * 它们通过 StartupPID 和下面的其他 pid_t 变量来跟踪。
 * （因此，任何给定的“特殊”子进程类型都不能有多个。
 * 对于可以有多个的任何子进程，我们使用 BackendList 条目。）
 */
typedef struct bkend
{
	pid_t		pid;			/* 后端的进程 ID */
	int32		cancel_key;		/* 此后端的取消密钥 */
	int			child_slot;		/* 此后端的 PMChildSlot（如果有） */
	int			bkend_type;		/* 子进程类型，见上文 */
	bool		dead_end;		/* 它是否会发送错误并退出？ */
	bool		bgworker_notify;	/* 获取 bgworker 启动/停止通知 */
	dlist_node	elem;			/* 在 BackendList 中的列表链接 */
} Backend;

static dlist_head BackendList = DLIST_STATIC_INIT(BackendList);

#ifdef EXEC_BACKEND
static Backend *ShmemBackendArray;
#endif

BackgroundWorker *MyBgworkerEntry = NULL;



/* 我们正在监听连接的套接字号 */
int			PostPortNumber;

/* Unix 套接字的目录名称 */
char	   *Unix_socket_directories;

/* TCP 监听地址 */
char	   *ListenAddresses;

/*
 * ReservedBackends 是保留给超级用户使用的后端数量。
 * 这个数字是从 MaxConnections 给出的池大小中减去的，
 * 所以非超级用户可用的后端槽数量为 (MaxConnections - ReservedBackends)。
 * 注意，这实际上意味着“如果可用连接数 <= ReservedBackends，
 * 只有超级用户可以建立新连接”——预先存在的超级用户连接不
 * 会计入限制。
 */
int			ReservedBackends;

/* 我们正在监听的套接字。 */
#define MAXLISTEN	64
static pgsocket ListenSocket[MAXLISTEN];

/*
 * 这些全局变量控制在某些后端转储核心的情况下，postmaster 的行为。
 * 通常，它会杀死死后端的所有同级，并重新初始化共享内存。
 * 通过指定 -s 或 -n，我们可以让 postmaster 停止（而不是杀死）同级，
 * 并且不重新初始化共享数据结构。（不过，Reinit 目前是死代码。）
 */
static bool Reinit = true;
static int	SendStop = false;

/* 还有更多选项变量 */
bool		EnableSSL = false;

int			PreAuthDelay = 0;
int			AuthenticationTimeout = 60;

bool		log_hostname;		/* 用于 ps 显示和日志记录 */
bool		Log_connections = false;
bool		Db_user_namespace = false;

bool		enable_bonjour = false;
char	   *bonjour_name;
bool		restart_after_crash = true;
bool		remove_temp_files_after_crash = true;

/* 特殊子进程的 PID；未运行时为 0 */
static pid_t StartupPID = 0,
			BgWriterPID = 0,
			CheckpointerPID = 0,
			WalWriterPID = 0,
			WalReceiverPID = 0,
			AutoVacPID = 0,
			PgArchPID = 0,
			SysLoggerPID = 0;

/* 启动进程的状态 */
typedef enum
{
	STARTUP_NOT_RUNNING,
	STARTUP_RUNNING,
	STARTUP_SIGNALED,			/* 我们向其发送了 SIGQUIT 或 SIGKILL */
	STARTUP_CRASHED
} StartupStatusEnum;

static StartupStatusEnum StartupStatus = STARTUP_NOT_RUNNING;

/* 启动/关闭状态 */
#define			NoShutdown		0
#define			SmartShutdown	1
#define			FastShutdown	2
#define			ImmediateShutdown	3

static int	Shutdown = NoShutdown;

static bool FatalError = false; /* T 如果正在从后端崩溃中恢复 */

/*
 * 我们使用一个简单的状态机来控制启动、关闭和
 * 崩溃恢复（这有点像关闭后再启动）。
 *
 * 在完成所有的主进程初始化工作后，我们进入 PM_STARTUP
 * 状态并启动启动进程。启动进程首先通过
 * 读取控制文件和其他初步初始化步骤开始。
 * 在正常启动或崩溃恢复后，启动进程以退出代码 0
 * 退出，我们切换到 PM_RUN 状态。但是，归档恢复
 * 是特别处理的，因为它需要更长的时间，并且我们希望在
 * 归档恢复期间支持热备用。
 *
 * 当启动进程准备好开始归档恢复时，它会向
 * 主进程发送信号，我们切换到 PM_RECOVERY 状态。背景写入器和
 * 检查点进程被启动，同时启动进程继续应用 WAL。
 * 如果启用了热备用，那么在达到 WAL
 * redo 的一致点后，启动进程再次向我们发送信号，
 * 我们切换到 PM_HOT_STANDBY 状态并开始接受连接以执行只读查询。
 * 当归档恢复完成后，启动进程以退出代码 0
 * 退出，我们切换到 PM_RUN 状态。
 *
 * 正常的子后台只能在 PM_RUN 或
 * PM_HOT_STANDBY 状态下启动。（connsAllowed 也可以限制启动。）
 * 在其他状态下，我们通过启动“死胡同”
 * 子进程来处理连接请求，这些子进程只会向客户端发送错误消息并
 * 退出。（我们在 BackendList 中跟踪这些进程，以便我们可以知道它们
 * 何时全部结束；这很重要，因为它们仍然连接到共享
 * 内存，并会妨碍尝试销毁共享内存段的操作，
 * 可能导致 SHMALL 失败，当我们试图创建一个新的时。）
 * 在 PM_WAIT_DEAD_END 状态下，我们正在等待所有的 dead_end 子进程
 * 从系统中释放，并且因此在最后一个现有子进程退出之前完全停止接受连接
 * 请求（希望这不会花费太长时间）。
 *
 * 注意，这个状态变量并不区分我们进入
 * PM_RUN 之后的状态 *原因* --- 必须咨询 Shutdown 和 FatalError
 * 才能找出这一点。在 PM_RECOVERY、PM_HOT_STANDBY、
 * 或 PM_RUN 状态下，FatalError 永远为假，也不
 * 在 PM_SHUTDOWN 状态下（因为我们在尝试从崩溃恢复时不进入这些
 * 状态）。它可以在 PM_STARTUP 状态下为真，因为
 * 我们不会清除它，直到我们成功启动了 WAL redo。
 */
typedef enum
{
	PM_INIT,					/* 主进程启动 */
	PM_STARTUP,					/* 等待启动子进程 */
	PM_RECOVERY,				/* 在归档恢复模式中 */
	PM_HOT_STANDBY,				/* 在热备用模式中 */
	PM_RUN,						/* 正常的“数据库是活着的”状态 */
	PM_STOP_BACKENDS,			/* 需要停止剩余的后台进程 */
	PM_WAIT_BACKENDS,			/* 等待活跃的后台进程退出 */
	PM_SHUTDOWN,				/* 等待检查点进程进行关闭 ckpt */
	PM_SHUTDOWN_2,				/* 等待归档程序和 WAL 发送者的
								 * 完成 */
	PM_WAIT_DEAD_END,			/* 等待 dead_end 子进程退出 */
	PM_NO_CHILDREN				/* 所有重要的子进程已经退出 */
} PMState;

static PMState pmState = PM_INIT;

/*
 * 在执行“智能关闭”时，我们限制新连接，但保持
 * 在 PM_RUN 或 PM_HOT_STANDBY 状态，直到所有客户端后台都消失。
 * connsAllowed 是一个子状态指示符，显示实际的限制。
 * 除非 pmState 是 PM_RUN 或 PM_HOT_STANDBY，否则这没有兴趣。
 */
static bool connsAllowed = true;

/* 立即关闭或子进程崩溃期间 SIGKILL 超时的开始时间 */
/* 零意味着超时没有运行 */
static time_t AbortStartTime = 0;

/* 该超时的长度 */
#define SIGKILL_CHILDREN_AFTER_SECS		5

static bool ReachedNormalRunning = false;	/* T 如果我们已经到达 PM_RUN */

bool		ClientAuthInProgress = false;	/* T 在新客户端
											 * 身份验证期间 */

bool		redirection_done = false;	/* stderr 被重定向为 syslogger？ */

/* 收到 START_AUTOVAC_LAUNCHER 信号 */
static volatile sig_atomic_t start_autovac_launcher = false;

/* 启动器需要被信号以传达某些条件 */
static volatile bool avlauncher_needs_signal = false;

/* 收到 START_WALRECEIVER 信号 */
static volatile sig_atomic_t WalReceiverRequested = false;

/* 当有一个需要启动的工作者时设置 */
static volatile bool StartWorkerNeeded = true;
static volatile bool HaveCrashedWorker = false;

#ifdef USE_SSL
/* 当且如果 SSL 已正确初始化时设置 */
static bool LoadedSSL = false;
#endif

#ifdef USE_BONJOUR
static DNSServiceRef bonjour_sdref = NULL;
#endif

/*
 * postmaster.c - 函数原型
 */
static void fc_CloseServerPorts(int fc_status, Datum fc_arg);
static void fc_unlink_external_pid_file(int fc_status, Datum fc_arg);
static void fc_getInstallationPaths(const char *fc_argv0);
static void fc_checkControlFile(void);
static Port *fc_ConnCreate(int fc_serverFd);
static void fc_ConnFree(Port *fc_port);
static void fc_reset_shared(void);
static void fc_SIGHUP_handler(SIGNAL_ARGS);
static void fc_pmdie(SIGNAL_ARGS);
static void fc_reaper(SIGNAL_ARGS);
static void fc_sigusr1_handler(SIGNAL_ARGS);
static void fc_process_startup_packet_die(SIGNAL_ARGS);
static void fc_dummy_handler(SIGNAL_ARGS);
static void fc_StartupPacketTimeoutHandler(void);
static void fc_CleanupBackend(int fc_pid, int fc_exitstatus);
static bool fc_CleanupBackgroundWorker(int fc_pid, int fc_exitstatus);
static void fc_HandleChildCrash(int fc_pid, int fc_exitstatus, const char *fc_procname);
static void fc_LogChildExit(int fc_lev, const char *fc_procname,
						 int fc_pid, int fc_exitstatus);
static void fc_PostmasterStateMachine(void);
static void fc_BackendInitialize(Port *fc_port);
static void fc_BackendRun(Port *fc_port) pg_attribute_noreturn();
static void fc_ExitPostmaster(int fc_status) pg_attribute_noreturn();
static int	fc_ServerLoop(void);
static int	fc_BackendStartup(Port *fc_port);
static int	fc_ProcessStartupPacket(Port *fc_port, bool fc_ssl_done, bool fc_gss_done);
static void fc_SendNegotiateProtocolVersion(List *fc_unrecognized_protocol_options);
static void fc_processCancelRequest(Port *fc_port, void *fc_pkt);
static int	fc_initMasks(fd_set *fc_rmask);
static void fc_report_fork_failure_to_client(Port *fc_port, int fc_errnum);
static CAC_state fc_canAcceptConnections(int fc_backend_type);
static bool fc_RandomCancelKey(int32 *fc_cancel_key);
static void fc_signal_child(pid_t fc_pid, int fc_signal);
static bool fc_SignalSomeChildren(int fc_signal, int fc_targets);
static void fc_TerminateChildren(int fc_signal);

#define SignalChildren(sig)			   fc_SignalSomeChildren(sig, BACKEND_TYPE_ALL)

static int	fc_CountChildren(int fc_target);
static bool fc_assign_backendlist_entry(RegisteredBgWorker *fc_rw);
static void fc_maybe_start_bgworkers(void);
static bool fc_CreateOptsFile(int fc_argc, char *fc_argv[], char *fc_fullprogname);
static pid_t fc_StartChildProcess(AuxProcType fc_type);
static void fc_StartAutovacuumWorker(void);
static void fc_MaybeStartWalReceiver(void);
static void fc_InitPostmasterDeathWatchHandle(void);

/*
 * 在当前主进程状态下，允许启动归档程序吗？
 *
 * 如果 WAL 归档始终启用，我们即使在恢复期间也允许启动归档程序。
 */
#define PgArchStartupAllowed()	\
	(((XLogArchivingActive() && pmState == PM_RUN) ||			\
	  (XLogArchivingAlways() &&									  \
	   (pmState == PM_RECOVERY || pmState == PM_HOT_STANDBY))) && \
	 PgArchCanRestart())

#ifdef EXEC_BACKEND

#ifdef WIN32
#define WNOHANG 0				/* 被忽略，因此任何整数值都可以 */

static pid_t waitpid(pid_t pid, int *exitstatus, int options);
static void WINAPI pgwin32_deadchild_callback(PVOID lpParameter, BOOLEAN TimerOrWaitFired);

static HANDLE win32ChildQueue;

typedef struct
{
	HANDLE		waitHandle;
	HANDLE		procHandle;
	DWORD		procId;
} win32_deadchild_waitinfo;
#endif							/* WIN32 */

static pid_t fc_backend_forkexec(Port *fc_port);
static pid_t fc_internal_forkexec(int fc_argc, char *fc_argv[], Port *fc_port);

/* 可以继承给客户端进程的套接字类型 */
#ifdef WIN32
typedef struct
{
	SOCKET		origsocket;		/* 原始套接字值，或 PGINVALID_SOCKET
								 * 如果不是套接字 */
	WSAPROTOCOL_INFO wsainfo;
} InheritableSocket;
#else
typedef int InheritableSocket;
#endif

/*
 * 结构包含传递给 exec:ed 后台的所有变量
 */
typedef struct
{
	Port		port;
	InheritableSocket portsocket;
	char		DataDir[MAXPGPATH];
	pgsocket	ListenSocket[MAXLISTEN];
	int32		MyCancelKey;
	int			MyPMChildSlot;
#ifndef WIN32
	unsigned long UsedShmemSegID;
#else
	void	   *ShmemProtectiveRegion;
	HANDLE		UsedShmemSegID;
#endif
	void	   *UsedShmemSegAddr;
	slock_t    *ShmemLock;
	VariableCache ShmemVariableCache;
	Backend    *ShmemBackendArray;
#ifndef HAVE_SPINLOCKS
	PGSemaphore *SpinlockSemaArray;
#endif
	int			NamedLWLockTrancheRequests;
	NamedLWLockTranche *NamedLWLockTrancheArray;
	LWLockPadded *MainLWLockArray;
	slock_t    *ProcStructLock;
	PROC_HDR   *ProcGlobal;
	PGPROC	   *AuxiliaryProcs;
	PGPROC	   *PreparedXactProcs;
	PMSignalData *PMSignalState;
	pid_t		PostmasterPid;
	TimestampTz PgStartTime;
	TimestampTz PgReloadTime;
	pg_time_t	first_syslogger_file_time;
	bool		redirection_done;
	bool		IsBinaryUpgrade;
	bool		query_id_enabled;
	int			max_safe_fds;
	int			MaxBackends;
#ifdef WIN32
	HANDLE		PostmasterHandle;
	HANDLE		initial_signal_pipe;
	HANDLE		syslogPipe[2];
#else
	int			postmaster_alive_fds[2];
	int			syslogPipe[2];
#endif
	char		my_exec_path[MAXPGPATH];
	char		pkglib_path[MAXPGPATH];
} BackendParameters;

static void fc_read_backend_variables(char *fc_id, Port *fc_port);
static void fc_restore_backend_variables(BackendParameters *fc_param, Port *fc_port);

#ifndef WIN32
static bool fc_save_backend_variables(BackendParameters *fc_param, Port *fc_port);
#else
static bool save_backend_variables(BackendParameters *param, Port *port,
								   HANDLE childProcess, pid_t childPid);
#endif

static void fc_ShmemBackendArrayAdd(Backend *fc_bn);
static void fc_ShmemBackendArrayRemove(Backend *fc_bn);
#endif							/*
 * PGSharedMemoryDetach
 *
 * 从共享内存段分离，如果仍然附加。这不是旨在由最初创建该段的进程显式调用（它将注册 on_shmem_exit 回调来做到这一点）。相反，这是为那些继承了附加并希望摆脱它的子进程而设。
 *
 * UsedShmemSegID 和 UsedShmemSegAddr 也是该例程的隐式参数，还有 AnonymousShmem 和 AnonymousShmemSize。
 */

#define StartupDataBase()		fc_StartChildProcess(StartupProcess)
#define StartArchiver()			fc_StartChildProcess(ArchiverProcess)
#define StartBackgroundWriter() fc_StartChildProcess(BgWriterProcess)
#define StartCheckpointer()		fc_StartChildProcess(CheckpointerProcess)
#define StartWalWriter()		fc_StartChildProcess(WalWriterProcess)
#define StartWalReceiver()		fc_StartChildProcess(WalReceiverProcess)

/* 检查子进程退出状态的宏 */
#define EXIT_STATUS_0(st)  ((st) == 0)
#define EXIT_STATUS_1(st)  (WIFEXITED(st) && WEXITSTATUS(st) == 1)
#define EXIT_STATUS_3(st)  (WIFEXITED(st) && WEXITSTATUS(st) == 3)

#ifndef WIN32
/*
 * 用于监控postmaster是否存活的管道文件描述符。
 * 第一个是POSTMASTER_FD_WATCH，第二个是POSTMASTER_FD_OWN。
 */
int			postmaster_alive_fds[2] = {-1, -1};
#else
/* 在Windows上为同一目的使用的postmaster进程句柄 */
HANDLE		PostmasterHandle;
#endif

/*
 * Postmaster主入口点
 */
void PostmasterMain(int fc_argc, char *fc_argv[])
{
	int			fc_opt;
	int			fc_status;
	char	   *fc_userDoption = NULL;
	bool		fc_listen_addr_saved = false;
	int			fc_i;
	char	   *fc_output_config_variable = NULL;

	InitProcessGlobals();

	PostmasterPid = MyProcPid;

	IsPostmasterEnvironment = true;

	/*
	 * 启动我们的win32信号实现
	 */
#ifdef WIN32
	pgwin32_signal_initialize();
#endif

	/*
	 * 我们在检查数据目录（参见checkDataDir()）之前不应创建任何文件或目录，
	 * 但以防万一，将umask设置为最严格（仅限于所有者）的权限。
	 *
	 * checkDataDir()将根据数据目录权限重置umask。
	 */
	umask(PG_MODE_MASK_OWNER);

	/*
	 * 默认情况下，postmaster中的palloc()请求将在PostmasterContext中分配，
	 * 这个空间可以被后端回收。
	 * 需要后端可用的分配数据应在TopMemoryContext中分配。
	 */
	PostmasterContext = AllocSetContextCreate(TopMemoryContext,
											  "Postmaster",
											  ALLOCSET_DEFAULT_SIZES);
	MemoryContextSwitchTo(PostmasterContext);

	/* 初始化安装文件的路径 */
	fc_getInstallationPaths(fc_argv[0]);

	/*
	 * 为postmaster进程设置信号处理程序。
	 *
	 * 在postmaster中，我们使用pqsignal_pm()而不是pqsignal()（所有子进程和客户端进程使用的）。
	 * 这有几个特殊行为：
	 *
	 * 1. 除非在Windows上，我们告诉sigaction()在信号处理程序的持续时间内屏蔽所有信号。
	 * 这比我们在信号处理程序中显式屏蔽/解除屏蔽的旧方法要快，
	 * 并且如果信号迅速到达，也应防止过度的堆栈消耗。
	 *
	 * 2. 我们不设置SA_RESTART标志。这是因为信号在所有时间都被屏蔽，
	 * 除非ServerLoop在等待某些事情发生，并且在那个窗口内，
	 * 我们希望信号能退出select(2)的等待，
	 * 以便ServerLoop在发生任何有趣的事情时可以作出响应。
	 * 在某些平台上，标记为SA_RESTART的信号不会导致select()等待结束。
	 *
	 * 子进程通常会希望SA_RESTART，因此pqsignal()会设置该标志。
	 * 我们期望子进程在解除信号的屏蔽之前设置自己的处理程序。
	 *
	 * 注意：更改此列表时，请检查对子进程信号处理设置的副作用。
	 * 参见tcop/postgres.c，bootstrap/bootstrap.c，postmaster/bgwriter.c，
	 * postmaster/walwriter.c，postmaster/autovacuum.c，postmaster/pgarch.c，
	 * postmaster/syslogger.c，postmaster/bgworker.c和postmaster/checkpointer.c。
	 */
	pqinitmask();
	PG_SETMASK(&BlockSig);

	pqsignal_pm(SIGHUP, fc_SIGHUP_handler);	/* 重新读取配置文件，并让子进程也这样做 */
	pqsignal_pm(SIGINT, fc_pmdie); /* 发送SIGTERM并关闭 */
	pqsignal_pm(SIGQUIT, fc_pmdie);	/* 发送SIGQUIT并退出 */
	pqsignal_pm(SIGTERM, fc_pmdie);	/* 等待子进程并关闭 */
	pqsignal_pm(SIGALRM, SIG_IGN);	/* ignored */
	pqsignal_pm(SIGPIPE, SIG_IGN);	/* ignored */
	pqsignal_pm(SIGUSR1, fc_sigusr1_handler);	/* 来自子进程的消息 */
	pqsignal_pm(SIGUSR2, fc_dummy_handler);	/* 未使用，保留给子进程 */
	pqsignal_pm(SIGCHLD, fc_reaper);	/* 处理子进程终止 */

#ifdef SIGURG

	/*
	 * 现在忽略SIGURG。子进程可能会更改这一点（参见InitializeLatchSupport），
	 * 但在它们等待latch之前，它们不会收到任何此类信号。
	 */
	pqsignal_pm(SIGURG, SIG_IGN);	/* ignored */
#endif

	/*
	 * Postgres中的其他地方不应触碰SIGTTIN/SIGTTOU处理。
	 * 我们在postmaster环境中忽略这些信号，以避免子进程因写入stderr而冻结的风险。
	 * 但对于独立后端，它们的默认处理是合理的。
	 * 因此，所有子进程应允许继承的设置保持不变。
	 */
#ifdef SIGTTIN
	pqsignal_pm(SIGTTIN, SIG_IGN);	/* ignored */
#endif
#ifdef SIGTTOU
	pqsignal_pm(SIGTTOU, SIG_IGN);	/* ignored */
#endif

	/* 忽略SIGXFSZ，以便上限 violations像磁盘满一样工作 */
#ifdef SIGXFSZ
	pqsignal_pm(SIGXFSZ, SIG_IGN);	/* ignored */
#endif

	/*
	 * 选项设置
	 */
	InitializeGUCOptions();

	opterr = 1;

	/*
	 * 解析命令行选项。注意：保持与
	 * tcop/postgres.c同步（选项集不应冲突）以及与
	 * main/main.c中的common help()函数同步。
	 */
#ifdef FDD
	while ((fc_opt = getopt(fc_argc, fc_argv, "B:K:M:bc:C:D:d:EeFf:h:ijk:lN:nOPp:r:S:sTt:W:-:")) != -1)
#else
	while ((fc_opt = getopt(fc_argc, fc_argv, "B:bc:C:D:d:EeFf:h:ijk:lN:nOPp:r:S:sTt:W:-:")) != -1)
#endif //FDD
	{
		switch (fc_opt)
		{
			case 'B':
				SetConfigOption("shared_buffers", optarg, PGC_POSTMASTER, PGC_S_ARGV);
				break;

			case 'b':
				/* 用于二进制升级的未记录标志 */
				IsBinaryUpgrade = true;
				break;

			case 'C':
				fc_output_config_variable = strdup(optarg);
				break;

			case 'D':
				fc_userDoption = strdup(optarg);
				break;

#ifdef FDD
			case 'K':
				SetConfigOption("encryption_key_command", optarg, PGC_POSTMASTER, PGC_S_ARGV);
				break;
			case 'M':
				g_tdepass = strdup(optarg);
				break;
#endif
			case 'd':
				set_debug_options(atoi(optarg), PGC_POSTMASTER, PGC_S_ARGV);
				break;

			case 'E':
				SetConfigOption("log_statement", "all", PGC_POSTMASTER, PGC_S_ARGV);
				break;

			case 'e':
				SetConfigOption("datestyle", "euro", PGC_POSTMASTER, PGC_S_ARGV);
				break;

			case 'F':
				SetConfigOption("fsync", "false", PGC_POSTMASTER, PGC_S_ARGV);
				break;

			case 'f':
				if (!set_plan_disabling_options(optarg, PGC_POSTMASTER, PGC_S_ARGV))
				{
					write_stderr("%s: invalid argument for option -f: \"%s\"\n",
								 progname, optarg);
					fc_ExitPostmaster(1);
				}
				break;

			case 'h':
				SetConfigOption("listen_addresses", optarg, PGC_POSTMASTER, PGC_S_ARGV);
				break;

			case 'i':
				SetConfigOption("listen_addresses", "*", PGC_POSTMASTER, PGC_S_ARGV);
				break;

			case 'j':
				/* 仅由交互式后端使用 */
				break;

			case 'k':
				SetConfigOption("unix_socket_directories", optarg, PGC_POSTMASTER, PGC_S_ARGV);
				break;

			case 'l':
				SetConfigOption("ssl", "true", PGC_POSTMASTER, PGC_S_ARGV);
				break;

			case 'N':
				SetConfigOption("max_connections", optarg, PGC_POSTMASTER, PGC_S_ARGV);
				break;

			case 'n':
				/* 异常退出后不重新初始化共享内存 */
				Reinit = false;
				break;

			case 'O':
				SetConfigOption("allow_system_table_mods", "true", PGC_POSTMASTER, PGC_S_ARGV);
				break;

			case 'P':
				SetConfigOption("ignore_system_indexes", "true", PGC_POSTMASTER, PGC_S_ARGV);
				break;

			case 'p':
				SetConfigOption("port", optarg, PGC_POSTMASTER, PGC_S_ARGV);
				break;

			case 'r':
				/* 仅由单用户后端使用 */
				break;

			case 'S':
				SetConfigOption("work_mem", optarg, PGC_POSTMASTER, PGC_S_ARGV);
				break;

			case 's':
				SetConfigOption("log_statement_stats", "true", PGC_POSTMASTER, PGC_S_ARGV);
				break;

			case 'T':

				/*
				 * 如果某些后端转储核心，向所有同伴发送SIGSTOP，
				 * 而不是SIGQUIT。这样狡猾的post_hacker就可以从每个人那里收集核心转储。
				 */
				SendStop = true;
				break;

			case 't':
				{
					const char *fc_tmp = get_stats_option_name(optarg);

					if (fc_tmp)
					{
						SetConfigOption(fc_tmp, "true", PGC_POSTMASTER, PGC_S_ARGV);
					}
					else
					{
						write_stderr("%s: invalid argument for option -t: \"%s\"\n",
									 progname, optarg);
						fc_ExitPostmaster(1);
					}
					break;
				}

			case 'W':
				SetConfigOption("post_auth_delay", optarg, PGC_POSTMASTER, PGC_S_ARGV);
				break;

			case 'c':
			case '-':
				{
					char	   *fc_name,
							   *fc_value;

					ParseLongOption(optarg, &fc_name, &fc_value);
					if (!fc_value)
					{
						if (fc_opt == '-')
							ereport(ERROR,
									(errcode(ERRCODE_SYNTAX_ERROR),
									 errmsg("--%s requires a value",
											optarg)));
						else
							ereport(ERROR,
									(errcode(ERRCODE_SYNTAX_ERROR),
									 errmsg("-c %s requires a value",
											optarg)));
					}

					SetConfigOption(fc_name, fc_value, PGC_POSTMASTER, PGC_S_ARGV);
					free(fc_name);
					if (fc_value)
						free(fc_value);
					break;
				}

			default:
				write_stderr("Try \"%s --help\" for more information.\n",
							 progname);
				fc_ExitPostmaster(1);
		}
	}
#ifdef FDD
	fdb_hideSensitiveArg("-K", 1);
    fdb_hideSensitiveArg("-M", 1);
#endif
	/*
	 * Postmaster不接受任何非选项开关参数。
	 */
	if (optind < fc_argc)
	{
		write_stderr("%s: invalid argument: \"%s\"\n",
					 progname, fc_argv[optind]);
		write_stderr("Try \"%s --help\" for more information.\n",
					 progname);
		fc_ExitPostmaster(1);
	}

	/*
	 * 定位正确的配置文件和数据目录，并首次读取
	 * postgresql.conf。
	 */
	if (!SelectConfigFiles(fc_userDoption, progname))
		fc_ExitPostmaster(2);

	if (fc_output_config_variable != NULL)
	{
		/*
		 * 如果这是一个运行时计算的 GUC，它尚未初始化，
		 * 目前的值没有用。然而，这是一个方便的地方来打印大多数 GUC 的
		 * 值，因为即使服务器已经
		 * 正在运行，安全地运行到这一点的 postmaster 启动。
		 * 对于少数无法提供有意义值的运行时计算 GUC，
		 * 我们将等到 postmaster 启动的稍后阶段再打印值。
		 * 对于这些 GUC，我们将无法在运行的服务器上使用 -C，但
		 * 现在使用这个选项将导致不正确的结果。
		 */
		int			fc_flags = GetConfigOptionFlags(fc_output_config_variable, true);

		if ((fc_flags & GUC_RUNTIME_COMPUTED) == 0)
		{
			/*
			 * 指定了 "-C guc"，因此打印 GUC 的值并退出。
			 * 不需要额外的权限检查，因为用户在数据目录中读取
			 * 。
			 */
			const char *fc_config_val = GetConfigOption(fc_output_config_variable,
													 false, false);

			puts(fc_config_val ? fc_config_val : "");
			fc_ExitPostmaster(0);
		}

		/*
		 * 运行时计算的 GUC 将在稍后打印。随着我们初始化
		 * 服务器启动序列，禁止任何可能出现在生成的输出中的
		 * 日志消息。FATAL 和更严重的消息是
		 * 有用的，尽管人们可能只期望至少 PANIC。LOG
		 * 条目被隐藏。
		 */
		SetConfigOption("log_min_messages", "FATAL", PGC_SUSET,
						PGC_S_OVERRIDE);
	}

	/* 验证 DataDir 看起来合理 */
	checkDataDir();

	/* 检查 pg_control 是否存在 */
	fc_checkControlFile();

	/* 将工作目录切换到它 */
	ChangeToDataDir();

	/*
	 * 检查 GUC 设置的无效组合。
	 */
	if (ReservedBackends >= MaxConnections)
	{
		write_stderr("%s: superuser_reserved_connections (%d) must be less than max_connections (%d)\n",
					 progname,
					 ReservedBackends, MaxConnections);
		fc_ExitPostmaster(1);
	}
	if (XLogArchiveMode > ARCHIVE_MODE_OFF && wal_level == WAL_LEVEL_MINIMAL)
		ereport(ERROR,
				(errmsg("WAL archival cannot be enabled when wal_level is \"minimal\"")));
	if (max_wal_senders > 0 && wal_level == WAL_LEVEL_MINIMAL)
		ereport(ERROR,
				(errmsg("WAL streaming (max_wal_senders > 0) requires wal_level \"replica\" or \"logical\"")));

	/*
	 * 其他一次性的内部完整性检查可以放在这里，如果它们很快。
	 * （将任何慢处理放在下面，在 postmaster.pid 创建之后。）
	 */
	if (!CheckDateTokenTables())
	{
		write_stderr("%s: invalid datetoken tables, please fix\n", progname);
		fc_ExitPostmaster(1);
	}

	/*
	 * 现在我们已经处理完 postmaster 参数，重置
	 * getopt(3) 库，以便它在子进程中正确工作。
	 */
	optind = 1;
#ifdef HAVE_INT_OPTRESET
	optreset = 1;				/* 有些系统也需要这个 */
#endif

	/* 用于调试：显示 postmaster 环境 */
	{
		extern char **environ;
		char	  **fc_p;

		ereport(DEBUG3,
				(errmsg_internal("%s: PostmasterMain: initial environment dump:",
								 progname)));
		ereport(DEBUG3,
				(errmsg_internal("-----------------------------------------")));
		for (fc_p = environ; *fc_p; ++fc_p)
			ereport(DEBUG3,
					(errmsg_internal("\t%s", *fc_p)));
		ereport(DEBUG3,
				(errmsg_internal("-----------------------------------------")));
	}

	/*
	 * 为数据目录创建锁定文件。
	 *
	 * 我们希望在尝试获取输入套接字之前这样做，因为数据目录的互锁
	 * 比套接字文件互锁更可靠（感谢决定将套接字文件放在 /tmp 的人 :-()。 
	 * 基于同样的原因，最好在 Unix 套接字之前抓取 TCP 套接字。
	 *
	 * 另请注意，这在内部设置了 on_proc_exit 函数，该函数
	 * 负责删除数据目录和套接字锁定文件；
	 * 因此，它必须在打开套接字之前发生，以便在退出时，
	 * 套接字锁定文件在 CloseServerPorts 运行后消失。
	 */
	CreateDataDirLockFile(true);

	/*
	 * 读取控制文件（用于错误检查和配置信息）。
	 *
	 * 由于我们验证控制文件的 CRC，这对我们需要运行时测试 CRC 支持指令的机器
	 * 有一个有用的副作用。 
	 * postmaster 会在启动时进行一次测试，然后它的子进程将继承正确的功能指针，
	 * 不需要重复测试。
	 */
	LocalProcessControlFile(false);

#ifdef FDD
	fdb_checkAndInitEncrypt();
#endif //FDD
	/*
	 * 注册应用程序启动器。 
	 * 在任何模块有机会占用后台工作者槽之前调用此操作可能是个好主意。
	 */
	ApplyLauncherRegister();

	/*
	 * 处理任何应该在 postmaster 启动时预加载的库 
	 */
	process_shared_preload_libraries();

	/*
	 * 初始化 SSL 库（如果指定）。
	 */
#ifdef USE_SSL
	if (EnableSSL)
	{
		(void) secure_initialize(true);
		LoadedSSL = true;
	}
#endif

	/*
	 * 现在由于可加载模块有机会更改任何 GUC， 
	 * 计算 MaxBackends。
	 */
	InitializeMaxBackends();

	/*
	 * 给预加载库一个请求额外共享内存的机会。
	 */
	process_shmem_requests();

	/*
	 * 现在由于可加载模块有机会请求额外共享内存， 
	 * 确定任何运行时计算的 GUC 的值， 
	 * 该值依赖于所需的共享内存量。
	 */
	InitializeShmemGUCs();

	/*
	 * 现在已经加载模块，我们可以处理在 wal_consistency_checking GUC 中指定的 
	 * 所有自定义资源管理器。
	 */
	InitializeWalConsistencyChecking();

	/*
	 * 如果指定了 -C 选项且其后跟一个运行时计算的 GUC， 
	 * 我们之前延迟打印其值，因为 GUC 尚未初始化。 
	 * 我们在锁定数据目录之前处理大多数 GUC 的 -C 
	 * 选项，以便在运行服务器上使用该选项。
	 * 然而，少数 GUC 是运行时计算的，
	 * 直到锁定数据目录后才有意义值，我们不能安全地计算它们的值，
	 * 是在运行服务器上。在这一点上，这些 GUC 应该正确初始化， 
	 * 但是我们还没有设置共享内存，所以现在是处理这些特定 GUC 的 -C 选项的好时机。
	 */
	if (fc_output_config_variable != NULL)
	{
		const char *fc_config_val = GetConfigOption(fc_output_config_variable,
												 false, false);

		puts(fc_config_val ? fc_config_val : "");
		fc_ExitPostmaster(0);
	}

	/*
	 * 设置共享内存和信号量。
	 */
	fc_reset_shared();

	/*
	 * 估算可打开文件的数量。 
	 * 这必须在设置信号量之后进行，
	 * 因为在某些平台上，信号量被视为打开文件。
	 */
	set_max_safe_fds();

	/*
	 * 设置堆栈深度检查的参考点。
	 */
	(void) set_stack_base();

	/*
	 * 初始化管道（或在 Windows 上的进程句柄）， 
	 * 允许子进程在 postmaster 死亡时从休眠中醒来。
	 */
	fc_InitPostmasterDeathWatchHandle();

#ifdef WIN32

	/*
	 * 初始化 I/O 完成端口，用于传递死去子进程的列表。
	 */
	win32ChildQueue = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1);
	if (win32ChildQueue == NULL)
		ereport(FATAL,
				(errmsg("could not create I/O completion port for child queue")));
#endif

#ifdef EXEC_BACKEND
	/* 将非默认的 GUC 设置写出供子进程使用 */
	write_nondefault_variables(PGC_POSTMASTER);

	/*
	 * 清理用于传递参数给子进程的临时目录 
	 * （见下面的 internal_forkexec）。 
	 * 我们必须在启动任何子进程之前完成此操作，否则会出现竞争条件： 
	 * 我们可能会在子进程能够读取参数之前删除参数文件。 
	 * 现在这样做应该是安全的，因为我们之前已验证 
	 * 此数据目录中没有冲突的 Postgres 进程。
	 */
	RemovePgTempFilesInDir(PG_TEMP_FILES_DIR, true, false);
#endif

	
/*
	 * 强制删除用于信号待机提升请求的文件。
	 * 否则，这些文件的存在会提前触发提升，
	 * 无论用户是否希望如此。
	 *
	 * 这些文件的删除通常是不必要的，因为它们只能在
	 * 待机提升的几秒钟内存在。然而存在竞争条件：如果执行
	 * pg_ctl promote 并在提升期间创建文件，这些文件可能会
	 * 保留，即使服务器已被启动为主节点。然后，如果新待机
	 * 使用从新主节点获取的备份启动，这些文件可能在服务器
	 * 启动时存在，必须被删除以避免意外提升。
	 *
	 * 请注意，提升信号文件需要在启动过程被调用之前
	 * 删除。因为，在此之后，它们可以被postmaster的
	 * SIGUSR1信号处理程序使用。
	 */
	RemovePromoteSignalFiles();

	/* 对logrotate信号文件执行相同操作 */
	RemoveLogrotateSignalFiles();

	/* 删除任何过时的文件，持有当前日志文件名。 */
	if (unlink(LOG_METAINFO_DATAFILE) < 0 && errno != ENOENT)
		ereport(LOG,
				(errcode_for_file_access(),
				 errmsg("could not remove file \"%s\": %m",
						LOG_METAINFO_DATAFILE)));

	/*
	 * 初始化输入套接字。
	 *
	 * 将它们全部标记为关闭，并设置一个on_proc_exit函数，
	 * 负责在postmaster关机时再次关闭这些套接字。
	 */
	for (fc_i = 0; fc_i < MAXLISTEN; fc_i++)
		ListenSocket[fc_i] = PGINVALID_SOCKET;

	on_proc_exit(fc_CloseServerPorts, 0);

	/*
	 * 如果启用，启动syslogger收集子进程
	 */
	SysLoggerPID = SysLogger_Start();

	/*
	 * 将 whereToSendOutput 从 DestDebug（其起始状态）重置为
	 * DestNone。这停止了 ereport 将日志消息发送到 stderr，除非
	 * Log_destination 允许。我们不会在 postmaster 完全启动之前执行此操作，
	 * 因为启动失败也可以报告到 stderr。
	 *
	 * 如果我们确实在禁用对 stderr 的日志记录，则首先发出一条日志消息
	 * 说明这一点，以为可能不记得其日志配置去往其他地方的用户提供线索。
	 */
	if (!(Log_destination & LOG_DESTINATION_STDERR))
		ereport(LOG,
				(errmsg("ending log output to stderr"),
				 errhint("Future log output will go to log destination \"%s\".",
						 Log_destination_string)));

	whereToSendOutput = DestNone;

	/*
	 * 在日志中报告服务器启动。虽然我们可以更早发出这一消息，
	 * 但最好在启动日志收集器之后执行此操作，如果我们打算使用一个。
	 */
	ereport(LOG,
			(errmsg("starting %s", PG_VERSION_STR)));

	/*
	 * 建立输入套接字。
	 */
	if (ListenAddresses)
	{
		char	   *fc_rawstring;
		List	   *fc_elemlist;
		ListCell   *fc_l;
		int			fc_success = 0;

		/* 需要一个可修改的 ListenAddresses 副本 */
		fc_rawstring = pstrdup(ListenAddresses);

		/* 将字符串解析为主机名列表 */
		if (!SplitGUCList(fc_rawstring, ',', &fc_elemlist))
		{
			/* 列表中的语法错误 */
			ereport(FATAL,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("invalid list syntax in parameter \"%s\"",
							"listen_addresses")));
		}

		foreach(fc_l, fc_elemlist)
		{
			char	   *fc_curhost = (char *) lfirst(fc_l);

			if (strcmp(fc_curhost, "*") == 0)
				fc_status = StreamServerPort(AF_UNSPEC, NULL,
										  (unsigned short) PostPortNumber,
										  NULL,
										  ListenSocket, MAXLISTEN);
			else
				fc_status = StreamServerPort(AF_UNSPEC, fc_curhost,
										  (unsigned short) PostPortNumber,
										  NULL,
										  ListenSocket, MAXLISTEN);

			if (fc_status == STATUS_OK)
			{
				fc_success++;
				/* 记录锁文件中的第一个成功的主机地址 */
				if (!fc_listen_addr_saved)
				{
					AddToDataDirLockFile(LOCK_FILE_LINE_LISTEN_ADDR, fc_curhost);
					fc_listen_addr_saved = true;
				}
			}
			else
				ereport(WARNING,
						(errmsg("could not create listen socket for \"%s\"",
								fc_curhost)));
		}

		if (!fc_success && fc_elemlist != NIL)
			ereport(FATAL,
					(errmsg("could not create any TCP/IP sockets")));

		list_free(fc_elemlist);
		pfree(fc_rawstring);
	}

#ifdef USE_BONJOUR
	/* 仅在我们打开了 TCP 套接字时注册 Bonjour */
	if (enable_bonjour && ListenSocket[0] != PGINVALID_SOCKET)
	{
		DNSServiceErrorType fc_err;

		/*
		 * 我们将 0 作为 interface_index 传递，这将导致在
		 * 所有“适用”的接口上注册。从 DNS-SD 文档中并不完全清楚
		 * 如果我们只绑定到可用网络接口的子集，这是否合适。
		 */
		fc_err = DNSServiceRegister(&bonjour_sdref,
								 0,
								 0,
								 bonjour_name,
								 "_postgresql._tcp.",
								 NULL,
								 NULL,
								 pg_hton16(PostPortNumber),
								 0,
								 NULL,
								 NULL,
								 NULL);
		if (fc_err != kDNSServiceErr_NoError)
			ereport(LOG,
					(errmsg("DNSServiceRegister() failed: error code %ld",
							(long) fc_err)));

		/*
		 * 我们不费心去读取 mDNS 守护进程的回复，并且我们期望它
		 * 会在套接字在 postmaster 终止时自动终止我们的注册。
		 * 所以这里没有更多的事情需要做。然而，保留 bonjour_sdref
		 * 是为了让派生子进程能够关闭其套接字的副本。
		 */
	}
#endif

#ifdef HAVE_UNIX_SOCKETS
	if (Unix_socket_directories)
	{
		char	   *fc_rawstring;
		List	   *fc_elemlist;
		ListCell   *fc_l;
		int			fc_success = 0;

		/* 需要一个可修改的 Unix_socket_directories 副本 */
		fc_rawstring = pstrdup(Unix_socket_directories);

		/* 将字符串解析为目录列表 */
		if (!SplitDirectoriesString(fc_rawstring, ',', &fc_elemlist))
		{
			/* 列表中的语法错误 */
			ereport(FATAL,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("invalid list syntax in parameter \"%s\"",
							"unix_socket_directories")));
		}

		foreach(fc_l, fc_elemlist)
		{
			char	   *fc_socketdir = (char *) lfirst(fc_l);

			fc_status = StreamServerPort(AF_UNIX, NULL,
									  (unsigned short) PostPortNumber,
									  fc_socketdir,
									  ListenSocket, MAXLISTEN);

			if (fc_status == STATUS_OK)
			{
				fc_success++;
				/* 记录锁文件中的第一个成功的 Unix 套接字 */
				if (fc_success == 1)
					AddToDataDirLockFile(LOCK_FILE_LINE_SOCKET_DIR, fc_socketdir);
			}
			else
				ereport(WARNING,
						(errmsg("could not create Unix-domain socket in directory \"%s\"",
								fc_socketdir)));
		}

		if (!fc_success && fc_elemlist != NIL)
			ereport(FATAL,
					(errmsg("could not create any Unix-domain sockets")));

		list_free_deep(fc_elemlist);
		pfree(fc_rawstring);
	}
#endif

	/*
	 * 检查我们是否有套接字可以监听
	 */
	if (ListenSocket[0] == PGINVALID_SOCKET)
		ereport(FATAL,
				(errmsg("no socket created for listening")));

	/*
	 * 如果没有有效的 TCP 端口，则为监听地址写入一个空行，
	 * 表明必须使用 Unix 套接字。请注意，这一行不会被添加到锁文件中，直到有一个套接字支持它。
	 */
	if (!fc_listen_addr_saved)
		AddToDataDirLockFile(LOCK_FILE_LINE_LISTEN_ADDR, "");

	/*
	 * 记录 postmaster 选项。我们将这项操作延迟到现在，以避免记录
	 * 虚假的选项（例如，不可用的端口号）。
	 */
	if (!fc_CreateOptsFile(fc_argc, fc_argv, my_exec_path))
		fc_ExitPostmaster(1);

	/*
	 * 如果请求，写入外部 PID 文件
	 */
	if (external_pid_file)
	{
		FILE	   *fc_fpidfile = fopen(external_pid_file, "w");

		if (fc_fpidfile)
		{
			fprintf(fc_fpidfile, "%d\n", MyProcPid);
			fclose(fc_fpidfile);

			/* 使 PID 文件对所有人可读 */
			if (chmod(external_pid_file, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) != 0)
				write_stderr("%s: could not change permissions of external PID file \"%s\": %s\n",
							 progname, external_pid_file, strerror(errno));
		}
		else
			write_stderr("%s: could not write external PID file \"%s\": %s\n",
						 progname, external_pid_file, strerror(errno));

		on_proc_exit(fc_unlink_external_pid_file, 0);
	}

	/*
	 * 删除旧的临时文件。在这一点上，在该目录中不能有其他
	 * Postgres 进程运行，因此这应该是安全的。
	 */
	RemovePgTempFiles();

	/*
	 * 初始化自动清理子系统（同样，目前没有进程启动）
	 */
	autovac_init();

	/*
	 * 加载客户端身份验证的配置文件。
	 */
	if (!load_hba())
	{
		/*
		 * 如果我们无法加载 HBA 文件，继续下去是没意义的，
		 * 因为在这种情况下无法连接到数据库。
		 */
		ereport(FATAL,
				(errmsg("could not load pg_hba.conf")));
	}
	if (!load_ident())
	{
		/*
		 * 我们可以在没有 IDENT 文件的情况下启动，尽管这意味着你
		 * 不能使用任何需要用户名映射的身份验证方法登录。 load_ident() 已经将错误
		 * 的详细信息记录到日志中。
		 */
	}

#ifdef HAVE_PTHREAD_IS_THREADED_NP

	/*
	 * 在 macOS 上，libintl 将 setlocale() 替换为一个版本，当
	 * 它的第二个参数为""且所有相关环境变量都未设置或为空时，会调用
	 * CFLocaleCopyCurrent()。CFLocaleCopyCurrent() 使
	 * 进程变得多线程。postmaster 调用 sigprocmask() 并且
	 * 调用 fork() 而没有立即 exec()，这在多线程程序中具有未定义
	 * 的行为。多线程 postmaster 是 Windows 上的正常情况，它既不提供 fork()
	 * 也不提供 sigprocmask()。目前，macOS 是唯一具有 pthread_is_threaded_np()
	 * 的平台，因此我们不必担心此提示在其他地方是否合适。
	 */
	if (pthread_is_threaded_np() != 0)
		ereport(FATAL,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("postmaster became multithreaded during startup"),
				 errhint("Set the LC_ALL environment variable to a valid locale.")));
#endif

	/*
	 * 记住 postmaster 启动时间
	 */
	PgStartTime = GetCurrentTimestamp();

	/*
	 * 在 postmaster.pid 文件中报告 postmaster 状态，以便 pg_ctl
	 * 查看发生了什么。
	 */
	AddToDataDirLockFile(LOCK_FILE_LINE_PM_STATUS, PM_STATUS_STARTING);

	/* 启动 bgwriter 和检查点程序，以便它们可以帮助恢复 */
	if (CheckpointerPID == 0)
		CheckpointerPID = StartCheckpointer();
	if (BgWriterPID == 0)
		BgWriterPID = StartBackgroundWriter();

	/*
	 * 我们准备好开始了……
	 */
	StartupPID = StartupDataBase();
	Assert(StartupPID != 0);
	StartupStatus = STARTUP_RUNNING;
	pmState = PM_STARTUP;

	/* 现在可能会调度某些工作进程启动 */
	fc_maybe_start_bgworkers();

	fc_status = fc_ServerLoop();

	/*
	 * ServerLoop 可能永远不应该返回，但如果它返回，
	 * 则关闭。
	 */
	fc_ExitPostmaster(fc_status != STATUS_OK);

	abort();					/* 未到达 */
}


/*
 * on_proc_exit 回调以关闭服务器的监听套接字
 */
static void fc_CloseServerPorts(int fc_status, Datum fc_arg)
{
	int			fc_i;

	/*
	 * 首先，显式关闭所有套接字文件描述符。我们过去只是让这
	 * 在 postmaster 退出时隐式发生，但最好在我们移除 postmaster.pid 锁文件之前关闭它们；
	 * 否则，如果新的 postmaster 想要重新使用 TCP 端口号，就会存在竞争条件。
	 */
	for (fc_i = 0; fc_i < MAXLISTEN; fc_i++)
	{
		if (ListenSocket[fc_i] != PGINVALID_SOCKET)
		{
			StreamClose(ListenSocket[fc_i]);
			ListenSocket[fc_i] = PGINVALID_SOCKET;
		}
	}

	/*
	 * 接下来，移除Unix套接字的任何文件系统条目。为了避免与incoming postmasters的竞争条件，这必须在关闭套接字后并在移除锁文件之前发生。
	 */
	RemoveSocketFiles();

	/*
	 * 我们在这里不处理套接字锁文件；这些将在稍后的on_proc_exit回调中移除。
	 */
}

/*
 * on_proc_exit回调以删除external_pid_file
 */
static void fc_unlink_external_pid_file(int fc_status, Datum fc_arg)
{
	if (external_pid_file)
		unlink(external_pid_file);
}


/*
 * 计算并检查作为安装一部分的文件的目录路径（根据postgres可执行文件自身位置推断得出）
 */
static void fc_getInstallationPaths(const char *fc_argv0)
{
	DIR		   *fc_pdir;

	/* 定位postgres可执行文件本身 */
	if (find_my_exec(fc_argv0, my_exec_path) < 0)
		ereport(FATAL,
				(errmsg("%s: could not locate my own executable path", fc_argv0)));

#ifdef EXEC_BACKEND
	/* 在更改工作目录之前定位可执行后端 */
	if (find_other_exec(fc_argv0, "postgres", PG_BACKEND_VERSIONSTR,
						postgres_exec_path) < 0)
		ereport(FATAL,
				(errmsg("%s: could not locate matching postgres executable",
						fc_argv0)));
#endif

	/*
	 * 定位pkglib目录——这一点必须尽早设置，以防我们尝试从postgresql.conf条目中加载任何模块。
	 */
	get_pkglib_path(my_exec_path, pkglib_path);

	/*
	 * 验证那里是否有可读目录；否则Postgres安装不完整或损坏。 （此失败的典型原因是postgres可执行文件已被移动或硬链接到某个不在安装lib/目录的兄弟目录中。）
	 */
	fc_pdir = AllocateDir(pkglib_path);
	if (fc_pdir == NULL)
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not open directory \"%s\": %m",
						pkglib_path),
				 errhint("This may indicate an incomplete PostgreSQL installation, or that the file \"%s\" has been moved away from its proper location.",
						 my_exec_path)));
	FreeDir(fc_pdir);

	/*
	 * XXX 是否同样值得检查share/目录？如果lib/目录存在，那么share/可能也存在。
	 */
}

/*
 * 检查pg_control是否在数据目录中的正确位置存在。
 *
 * 在这里没有尝试验证pg_control的内容。这只是一个理智检查，以查看我们是否正在查看一个真实的数据目录。
 */
static void fc_checkControlFile(void)
{
	char		fc_path[MAXPGPATH];
	FILE	   *fc_fp;

	snprintf(fc_path, sizeof(fc_path), "%s/global/pg_control", DataDir);

	fc_fp = AllocateFile(fc_path, PG_BINARY_R);
	if (fc_fp == NULL)
	{
		write_stderr("%s: could not find the database system\n"
					 "Expected to find it in the directory \"%s\",\n"
					 "but could not open file \"%s\": %s\n",
					 progname, DataDir, fc_path, strerror(errno));
		fc_ExitPostmaster(2);
	}
	FreeFile(fc_fp);
}

/*
 * 确定我们应该让ServerLoop休眠多长时间。
 *
 * 在正常情况下，我们最多等待一分钟，以确保即使没有请求到达，ServerLoop处理的其他后台任务也能完成。 然而，如果有后台工作者等待启动，我们实际上不会休眠，以便它们能够迅速得到服务。 其他例外情况在代码中显示。
 */
static void fc_DetermineSleepTime(struct timeval *fc_timeout)
{
	TimestampTz fc_next_wakeup = 0;

	/*
	 * 正常情况：要么根本没有后台工作者，要么我们处于关机序列中（在此期间我们完全忽略bgworkers）。
	 */
	if (Shutdown > NoShutdown ||
		(!StartWorkerNeeded && !HaveCrashedWorker))
	{
		if (AbortStartTime != 0)
		{
			/* 剩余时间以便中止；如果时间已经到，限制为0 */
			fc_timeout->tv_sec = SIGKILL_CHILDREN_AFTER_SECS -
				(time(NULL) - AbortStartTime);
			fc_timeout->tv_sec = Max(fc_timeout->tv_sec, 0);
			fc_timeout->tv_usec = 0;
		}
		else
		{
			fc_timeout->tv_sec = 60;
			fc_timeout->tv_usec = 0;
		}
		return;
	}

	if (StartWorkerNeeded)
	{
		fc_timeout->tv_sec = 0;
		fc_timeout->tv_usec = 0;
		return;
	}

	if (HaveCrashedWorker)
	{
		slist_mutable_iter fc_siter;

		/*
		 * 当有崩溃的bgworkers时，我们只休眠足够长的时间，以便在它们请求时重新启动它们。 扫描列表以确定根据最近的崩溃时间和请求的重新启动间隔的所有唤醒时间中的最小值。
		 */
		slist_foreach_modify(fc_siter, &BackgroundWorkerList)
		{
			RegisteredBgWorker *fc_rw;
			TimestampTz fc_this_wakeup;

			fc_rw = slist_container(RegisteredBgWorker, rw_lnode, fc_siter.cur);

			if (fc_rw->rw_crashed_at == 0)
				continue;

			if (fc_rw->rw_worker.bgw_restart_time == BGW_NEVER_RESTART
				|| fc_rw->rw_terminate)
			{
				ForgetBackgroundWorker(&fc_siter);
				continue;
			}

			fc_this_wakeup = TimestampTzPlusMilliseconds(fc_rw->rw_crashed_at,
													  1000L * fc_rw->rw_worker.bgw_restart_time);
			if (fc_next_wakeup == 0 || fc_this_wakeup < fc_next_wakeup)
				fc_next_wakeup = fc_this_wakeup;
		}
	}

	if (fc_next_wakeup != 0)
	{
		long		fc_secs;
		int			fc_microsecs;

		TimestampDifference(GetCurrentTimestamp(), fc_next_wakeup,
							&fc_secs, &fc_microsecs);
		fc_timeout->tv_sec = fc_secs;
		fc_timeout->tv_usec = fc_microsecs;

		/* 确保我们不超过一分钟 */
		if (fc_timeout->tv_sec > 60)
		{
			fc_timeout->tv_sec = 60;
			fc_timeout->tv_usec = 0;
		}
	}
	else
	{
		fc_timeout->tv_sec = 60;
		fc_timeout->tv_usec = 0;
	}
}

/*
 * postmaster的主要空闲循环
 *
 * 注意：需要在屏蔽信号的情况下调用
 */
static int fc_ServerLoop(void)
{
	fd_set		fc_readmask;
	int			fc_nSockets;
	time_t		fc_last_lockfile_recheck_time,
				fc_last_touch_time;

	fc_last_lockfile_recheck_time = fc_last_touch_time = time(NULL);

	fc_nSockets = fc_initMasks(&fc_readmask);

	for (;;)
	{
		fd_set		fc_rmask;
		int			fc_selres;
		time_t		fc_now;

		/*
		 * 等待连接请求到达。
		 *
		 * 我们阻止所有信号，除了在睡眠时。这使得信号处理程序在执行时再次阻止所有信号时可以安全地进行非平凡的工作。
		 *
		 * 如果我们处于PM_WAIT_DEAD_END状态，那么我们不想接受任何新连接，所以我们不调用select()，而是直接睡眠。
		 */
		memcpy((char *) &fc_rmask, (char *) &fc_readmask, sizeof(fd_set));

		if (pmState == PM_WAIT_DEAD_END)
		{
			PG_SETMASK(&UnBlockSig);

			pg_usleep(100000L); /* 100毫秒似乎是合理的 */
			fc_selres = 0;

			PG_SETMASK(&BlockSig);
		}
		else
		{
			/* 每次必须设置超时时间；某些操作系统会更改它！ */
			struct timeval fc_timeout;

			/* 需要在屏蔽信号的情况下运行！ */
			fc_DetermineSleepTime(&fc_timeout);

			PG_SETMASK(&UnBlockSig);

			fc_selres = select(fc_nSockets, &fc_rmask, NULL, NULL, &fc_timeout);

			PG_SETMASK(&BlockSig);
		}

		/* 现在检查select()结果 */
		if (fc_selres < 0)
		{
			if (errno != EINTR && errno != EWOULDBLOCK)
			{
				ereport(LOG,
						(errcode_for_socket_access(),
						 errmsg("select() failed in postmaster: %m")));
				return STATUS_ERROR;
			}
		}

		/*
		 * 在我们的任何套接字上有新连接待处理吗？如果有，fork一个子进程来处理它。
		 */
		if (fc_selres > 0)
		{
			int			fc_i;

			for (fc_i = 0; fc_i < MAXLISTEN; fc_i++)
			{
				if (ListenSocket[fc_i] == PGINVALID_SOCKET)
					break;
				if (FD_ISSET(ListenSocket[fc_i], &fc_rmask))
				{
					Port	   *fc_port;

					fc_port = fc_ConnCreate(ListenSocket[fc_i]);
					if (fc_port)
					{
						fc_BackendStartup(fc_port);

						/*
						 * 在此进程中我们不再需要打开的套接字或端口结构
						 */
						StreamClose(fc_port->sock);
						fc_ConnFree(fc_port);
					}
				}
			}
		}

		/* 如果我们失去了日志收集器，尝试启动一个新的 */
#ifdef FDD
		if (SysLoggerPID == 0)//需要考虑未开启独立日志进程功能，而开启了审计功能的场景
		{
			SysLoggerPID = SysLogger_Start();
		}
#else
		if (SysLoggerPID == 0 && Logging_collector)
			SysLoggerPID = SysLogger_Start();
#endif

		/*
		 * 如果没有后台写进程正在运行，并且我们不处于阻止它的状态，启动一个。 如果失败也没关系，我们只会稍后再试。 检查点也一样。
		 */
		if (pmState == PM_RUN || pmState == PM_RECOVERY ||
			pmState == PM_HOT_STANDBY || pmState == PM_STARTUP)
		{
			if (CheckpointerPID == 0)
				CheckpointerPID = StartCheckpointer();
			if (BgWriterPID == 0)
				BgWriterPID = StartBackgroundWriter();
		}

		/*
		 * 同样，如果我们失去了walwriter进程，尝试启动一个新的。 但这在正常操作中是必需的（否则我们无法写入任何新的WAL）。
		 */
		if (WalWriterPID == 0 && pmState == PM_RUN)
			WalWriterPID = StartWalWriter();

		/*
		 * 如果我们失去了autovacuum启动器，尝试启动一个新的。 我们不希望autovacuum在二进制升级模式下运行，因为autovacuum可能会在物理文件放置之前更新空表的relfrozenxid。
		 */
		if (!IsBinaryUpgrade && AutoVacPID == 0 &&
			(AutoVacuumingActive() || start_autovac_launcher) &&
			pmState == PM_RUN)
		{
			AutoVacPID = StartAutoVacLauncher();
			if (AutoVacPID != 0)
				start_autovac_launcher = false; /* 信号已处理 */
		}

		/* 如果我们失去了归档器，尝试启动一个新的。 */
		if (PgArchPID == 0 && PgArchStartupAllowed())
			PgArchPID = StartArchiver();

		/* 如果我们需要给autovacuum启动器发送信号，请立即这样做 */
		if (avlauncher_needs_signal)
		{
			avlauncher_needs_signal = false;
			if (AutoVacPID != 0)
				kill(AutoVacPID, SIGUSR2);
		}

		/* 如果我们需要启动一个WAL接收器，请立即尝试这样做 */
		if (WalReceiverRequested)
			fc_MaybeStartWalReceiver();

		/* 如果需要，启动其他工作进程 */
		if (StartWorkerNeeded || HaveCrashedWorker)
			fc_maybe_start_bgworkers();

#ifdef HAVE_PTHREAD_IS_THREADED_NP

		/*
		 * 在启用断言的情况下，定期检查是否出现了
		 * 额外线程。所有构建在启动和退出时都会进行检查。
		 */
		Assert(pthread_is_threaded_np() == 0);
#endif

		/*
		 * 最后，检查一下是否是时候做一些我们不想在每次循环中都执行的事情，因为它们有点昂贵。
		 * 请注意，这些任务的执行时间最多会有一分钟的差异，因为DetermineSleepTime()会
		 * 让我们最多睡眠那么长时间；除了SIGKILL超时，它有特定的逻辑。
		 */
		fc_now = time(NULL);

		/*
		 * 如果我们已经向子进程发送了SIGQUIT，并且它们关闭速度较慢，
		 * 是时候发送SIGKILL了。这种情况通常不会发生，但在某些条件下，后端在关闭时可能会卡住。
		 * 这是将它们解困的最后手段。
		 *
		 * 请注意，我们在从进程崩溃恢复期间也会这样做。
		 */
		if ((Shutdown >= ImmediateShutdown || (FatalError && !SendStop)) &&
			AbortStartTime != 0 &&
			(fc_now - AbortStartTime) >= SIGKILL_CHILDREN_AFTER_SECS)
		{
			/* 我们之前对它们很温和，但现在不再如此 */
			ereport(LOG,
					(errmsg("issuing SIGKILL to recalcitrant children")));
			fc_TerminateChildren(SIGKILL);
			/* 重置标志，以便我们不会再次发送SIGKILL */
			AbortStartTime = 0;
		}

		/*
		 * 每分钟验证一次postmaster.pid文件是否没有被删除或覆盖。
		 * 如果被删除了，我们强制关闭。这可以避免在其数据库消失后，
		 * postmaster和子进程仍然在运行，并可能在同一位置创建新数据库集群时造成问题。
		 * 这还提供了一些保护，防止DBA愚蠢地删除postmaster.pid并手动启动新的postmaster。
		 * 数据损坏可能会因此发生，但我们可以通过尽快中止来最小化损害。
		 */
		if (fc_now - fc_last_lockfile_recheck_time >= 1 * SECS_PER_MINUTE)
		{
			if (!RecheckDataDirLockFile())
			{
				ereport(LOG,
						(errmsg("performing immediate shutdown because data directory lock file is invalid")));
				kill(MyProcPid, SIGQUIT);
			}
			fc_last_lockfile_recheck_time = fc_now;
		}

		/*
		 * 每58分钟触碰Unix套接字和锁文件，以确保
		 * 它们不会被过于积极的/tmp清理任务删除。我们假设
		 * 没有人运行截止时间少于一小时的清理器...
		 */
		if (fc_now - fc_last_touch_time >= 58 * SECS_PER_MINUTE)
		{
			TouchSocketFiles();
			TouchSocketLockFiles();
			fc_last_touch_time = fc_now;
		}
	}
}

/*
 * 初始化我们正在监听的端口的select()掩码。
 * 返回需要监听的套接字数量。
 */
static int fc_initMasks(fd_set *fc_rmask)
{
	int			fc_maxsock = -1;
	int			fc_i;

	FD_ZERO(fc_rmask);

	for (fc_i = 0; fc_i < MAXLISTEN; fc_i++)
	{
		int			fc_fd = ListenSocket[fc_i];

		if (fc_fd == PGINVALID_SOCKET)
			break;
		FD_SET(fc_fd, fc_rmask);

		if (fc_fd > fc_maxsock)
			fc_maxsock = fc_fd;
	}

	return fc_maxsock + 1;
}


/*
 * 读取客户端的启动数据包并根据它执行相应的操作。
 *
 * 返回STATUS_OK或STATUS_ERROR，或者可能调用ereport(FATAL)并且
 * 完全不返回。
 *
 * （请注意，ereport(FATAL)的信息是发送给客户端的，因此只有在
 * 您希望这样做时才使用。如果您不想发送任何内容给客户端，通常适当
 * 的做法是返回STATUS_ERROR，这在我们检测到通信故障时通常是合适的。）
 *
 * 当加密层（目前为TLS或GSSAPI）的协商完成时，设置ssl_done和/或gss_done。
 * 成功协商任一加密层都会同时设置这两个标志，但被拒绝的协商仅会设置
 * 该层的标志，因为客户端可能希望尝试另一层。我们在这里不应假设
 * 客户端发出请求的顺序。
 */
static int fc_ProcessStartupPacket(Port *fc_port, bool fc_ssl_done, bool fc_gss_done)
{
	int32		fc_len;
	char	   *fc_buf;
	ProtocolVersion fc_proto;
	MemoryContext fc_oldcontext;

	pq_startmsgread();

	/*
	 * 单独抓取长度字的第一个字节，以便我们可以判断
	 * 是否根本没有数据或数据包不完整。 （这听起来
	 * 可能效率低下，但实际上并不是，因为在
	 * pqcomm.c中的缓冲）
	 */
	if (pq_getbytes((char *) &fc_len, 1) == EOF)
	{
		/*
		 * 如果我们根本没有数据，不要让日志充满抱怨；
		 * 这种情况通常发生在合法原因下。一个例子是，我们可能在响应
		 * NEGOTIATE_SSL_CODE后来到这里，如果客户端不喜欢我们的响应，
		 * 它可能会直接断掉连接。服务监控软件通常也只是打开和关闭连接
		 * 而不发送任何内容。 （端口扫描器也是如此，可能不那么善良，但这
		 * 实在不是我们的工作去注意这些情况。）
		 */
		return STATUS_ERROR;
	}

	if (pq_getbytes(((char *) &fc_len) + 1, 3) == EOF)
	{
		/* 得到一个部分长度字，所以对此发出警告 */
		if (!fc_ssl_done && !fc_gss_done)
			ereport(COMMERROR,
					(errcode(ERRCODE_PROTOCOL_VIOLATION),
					 errmsg("incomplete startup packet")));
		return STATUS_ERROR;
	}

	fc_len = pg_ntoh32(fc_len);
	fc_len -= 4;

	if (fc_len < (int32) sizeof(ProtocolVersion) ||
		fc_len > MAX_STARTUP_PACKET_LENGTH)
	{
		ereport(COMMERROR,
				(errcode(ERRCODE_PROTOCOL_VIOLATION),
				 errmsg("invalid length of startup packet")));
		return STATUS_ERROR;
	}

	/*
	 * 分配空间以容纳启动数据包，以及一个初始化为零的额外字节。
	 * 这确保了我们将所有字符串在数据包内进行空终止。
	 */
	fc_buf = palloc(fc_len + 1);
	fc_buf[fc_len] = '\0';

	if (pq_getbytes(fc_buf, fc_len) == EOF)
	{
		ereport(COMMERROR,
				(errcode(ERRCODE_PROTOCOL_VIOLATION),
				 errmsg("incomplete startup packet")));
		return STATUS_ERROR;
	}
	pq_endmsgread();

	/*
	 * 第一个字段是协议版本号或特殊请求代码。
	 */
	fc_port->proto = fc_proto = pg_ntoh32(*((ProtocolVersion *) fc_buf));

	if (fc_proto == CANCEL_REQUEST_CODE)
	{
		if (fc_len != sizeof(CancelRequestPacket))
		{
			ereport(COMMERROR,
					(errcode(ERRCODE_PROTOCOL_VIOLATION),
					 errmsg("invalid length of startup packet")));
			return STATUS_ERROR;
		}
		fc_processCancelRequest(fc_port, fc_buf);
		/* 这并不是真正的错误，但我们不想继续进行 */
		return STATUS_ERROR;
	}

	if (fc_proto == NEGOTIATE_SSL_CODE && !fc_ssl_done)
	{
		char		fc_SSLok;

#ifdef USE_SSL
		/* 在禁用或Unix套接字上不支持SSL */
		if (!LoadedSSL || fc_port->laddr.addr.ss_family == AF_UNIX)
			fc_SSLok = 'N';
		else
			fc_SSLok = 'S';		/* 支持SSL */
#else
		fc_SSLok = 'N';			/* 不支持SSL */
#endif

retry1:
		if (send(fc_port->sock, &fc_SSLok, 1, 0) != 1)
		{
			if (errno == EINTR)
				goto retry1;	/* 如果被中断，只需重试 */
			ereport(COMMERROR,
					(errcode_for_socket_access(),
					 errmsg("failed to send SSL negotiation response: %m")));
			return STATUS_ERROR;	/* 关闭连接 */
		}

#ifdef USE_SSL
		if (fc_SSLok == 'S' && secure_open_server(fc_port) == -1)
			return STATUS_ERROR;
#endif

		/*
		 * 此时我们应该没有任何已缓冲的数据。如果有，
		 * 那是在我们执行SSL握手之前接收的，因此它未被加密，
		 * 实际上可能是被中间人注入的。
		 * 我们将向客户端报告这一情况。
		 */
		if (pq_buffer_has_data())
			ereport(FATAL,
					(errcode(ERRCODE_PROTOCOL_VIOLATION),
					 errmsg("received unencrypted data after SSL request"),
					 errdetail("This could be either a client-software bug or evidence of an attempted man-in-the-middle attack.")));

		/*
		 * 常规启动数据包、取消等数据包应在后面跟随，但不
		 * 是另一个SSL协商请求，GSS请求应仅在SSL被拒绝
		 * 后跟随（客户端可以以任意顺序协商）
		 */
		return fc_ProcessStartupPacket(fc_port, true, fc_SSLok == 'S');
	}
	else if (fc_proto == NEGOTIATE_GSS_CODE && !fc_gss_done)
	{
		char		fc_GSSok = 'N';

#ifdef ENABLE_GSS
		/* 在Unix套接字上不支持GSSAPI加密 */
		if (fc_port->laddr.addr.ss_family != AF_UNIX)
			fc_GSSok = 'G';
#endif

		while (send(fc_port->sock, &fc_GSSok, 1, 0) != 1)
		{
			if (errno == EINTR)
				continue;
			ereport(COMMERROR,
					(errcode_for_socket_access(),
					 errmsg("failed to send GSSAPI negotiation response: %m")));
			return STATUS_ERROR;	/* 关闭连接 */
		}

#ifdef ENABLE_GSS
		if (fc_GSSok == 'G' && secure_open_gssapi(fc_port) == -1)
			return STATUS_ERROR;
#endif

		
/*
		 * 在这一点上，我们应该没有数据被缓冲。如果有的话，
		 * 那是在我们执行GSS握手之前接收到的，因此没有被加密，
		 * 并且可能确实是被中间人注入的。
		 * 我们将这一情况报告给客户端。
		 */
		if (pq_buffer_has_data())
			ereport(FATAL,
					(errcode(ERRCODE_PROTOCOL_VIOLATION),
					 errmsg("received unencrypted data after GSSAPI encryption request"),
					 errdetail("This could be either a client-software bug or evidence of an attempted man-in-the-middle attack.")));

		/*
		 * 正常的启动包、取消包等应该在后面，但不应该
		 * 另一个GSS协商请求，只有在GSS被拒绝时才应遵循SSL请求
		 * （客户端可以按任意顺序进行协商）
		 */
		return fc_ProcessStartupPacket(fc_port, fc_GSSok == 'G', true);
	}

	/* 可以在这里添加额外的特殊包类型 */

	/*
	 * 现在设置FrontendProtocol，以便ereport()知道如果
	 * 启动期间失败时要发送什么格式。
	 */
	FrontendProtocol = fc_proto;

	/* 检查主要协议版本是否在范围内。 */
	if (PG_PROTOCOL_MAJOR(fc_proto) < PG_PROTOCOL_MAJOR(PG_PROTOCOL_EARLIEST) ||
		PG_PROTOCOL_MAJOR(fc_proto) > PG_PROTOCOL_MAJOR(PG_PROTOCOL_LATEST))
		ereport(FATAL,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("unsupported frontend protocol %u.%u: server supports %u.0 to %u.%u",
						PG_PROTOCOL_MAJOR(fc_proto), PG_PROTOCOL_MINOR(fc_proto),
						PG_PROTOCOL_MAJOR(PG_PROTOCOL_EARLIEST),
						PG_PROTOCOL_MAJOR(PG_PROTOCOL_LATEST),
						PG_PROTOCOL_MINOR(PG_PROTOCOL_LATEST))));

	/*
	 * 现在从启动数据包中获取参数并将它们保存到 Port 结构中。
	 * 附加到 Port 结构的所有数据结构必须在 TopMemoryContext 中分配，以便它们在
	 * 运行的后端中保持可用（即使 PostmasterContext 被销毁后也如此）。我们不需要担心
	 * 在失败时泄漏这段存储，因为我们已不再处于 postmaster 进程中。
	 */
	fc_oldcontext = MemoryContextSwitchTo(TopMemoryContext);

	/* 处理协议版本 3 启动数据包 */
	{
		int32		fc_offset = sizeof(ProtocolVersion);
		List	   *fc_unrecognized_protocol_options = NIL;

		/*
		 * 扫描数据包主体以查找名称/选项对。我们可以假设数据包主体内的任何字符串
		 * 都是以空字符终止的，这要归功于上面清零的额外字节。
		 */
		fc_port->guc_options = NIL;

		while (fc_offset < fc_len)
		{
			char	   *fc_nameptr = fc_buf + fc_offset;
			int32		fc_valoffset;
			char	   *fc_valptr;

			if (*fc_nameptr == '\0')
				break;			/* 找到数据包结束符 */
			fc_valoffset = fc_offset + strlen(fc_nameptr) + 1;
			if (fc_valoffset >= fc_len)
				break;			/* 缺少值，稍后会进行抱怨 */
			fc_valptr = fc_buf + fc_valoffset;

			if (strcmp(fc_nameptr, "database") == 0)
				fc_port->database_name = pstrdup(fc_valptr);
			else if (strcmp(fc_nameptr, "user") == 0)
				fc_port->user_name = pstrdup(fc_valptr);
			else if (strcmp(fc_nameptr, "options") == 0)
				fc_port->cmdline_options = pstrdup(fc_valptr);
			else if (strcmp(fc_nameptr, "replication") == 0)
			{
				/*
				 * 由于向后兼容性考虑，复制参数是一个混合体，允许值
				 * 可以是布尔型或字符串 'database'。后者
				 * 连接到特定的数据库，这对逻辑解码而言是
				 * 必需的。
				 */
				if (strcmp(fc_valptr, "database") == 0)
				{
					am_walsender = true;
					am_db_walsender = true;
				}
				else if (!parse_bool(fc_valptr, &am_walsender))
					ereport(FATAL,
							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
							 errmsg("invalid value for parameter \"%s\": \"%s\"",
									"replication",
									fc_valptr),
							 errhint("Valid values are: \"false\", 0, \"true\", 1, \"database\".")));
			}
			else if (strncmp(fc_nameptr, "_pq_.", 5) == 0)
			{
				/*
				 * 任何以 _pq_. 开头的选项都保留用于协议级选项，
				 * 但目前没有定义这样的选项。
				 */
				fc_unrecognized_protocol_options =
					lappend(fc_unrecognized_protocol_options, pstrdup(fc_nameptr));
			}
			else
			{
				/* 假设这是一个通用 GUC 选项 */
				fc_port->guc_options = lappend(fc_port->guc_options,
											pstrdup(fc_nameptr));
				fc_port->guc_options = lappend(fc_port->guc_options,
											pstrdup(fc_valptr));

				/*
				 * 如果遇到 application_name，则将其复制到端口。
				 * 这样做是为了我们可以在连接授权消息中记录
				 * application_name。请注意，GUC 将被使用，但我们
				 * 尚未完成 GUC 设置。
				 */
				if (strcmp(fc_nameptr, "application_name") == 0)
				{
					char	   *fc_tmp_app_name = pstrdup(fc_valptr);

					pg_clean_ascii(fc_tmp_app_name);

					fc_port->application_name = fc_tmp_app_name;
				}
			}
			fc_offset = fc_valoffset + strlen(fc_valptr) + 1;
		}

		/*
		 * 如果我们没有在给定的数据包长度的末尾找到数据包结束符，
		 * 请提出抱怨。
		 */
		if (fc_offset != fc_len - 1)
			ereport(FATAL,
					(errcode(ERRCODE_PROTOCOL_VIOLATION),
					 errmsg("invalid startup packet layout: expected terminator as last byte")));

		/*
		 * 如果客户端请求了更新的协议版本，或者如果客户端请求了我们
		 * 未识别的任何协议选项，请让他们知道我们支持的最新次要协议
		 * 版本以及任何未识别选项的名称。
		 */
		if (PG_PROTOCOL_MINOR(fc_proto) > PG_PROTOCOL_MINOR(PG_PROTOCOL_LATEST) ||
			fc_unrecognized_protocol_options != NIL)
			fc_SendNegotiateProtocolVersion(fc_unrecognized_protocol_options);
	}

	/* 检查是否提供了用户名。 */
	if (fc_port->user_name == NULL || fc_port->user_name[0] == '\0')
		ereport(FATAL,
				(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
				 errmsg("no PostgreSQL user name specified in startup packet")));

	/* 数据库默认为用户名。 */
	if (fc_port->database_name == NULL || fc_port->database_name[0] == '\0')
		fc_port->database_name = pstrdup(fc_port->user_name);

	if (Db_user_namespace)
	{
		/*
		 * 如果是 user@，那么它是一个全局用户，去掉 '@'。我们只想在
		 * 用户字符串的末尾有 '@'，且在之前没有 '@' 的情况下执行此操作
		 * ，否则他们可能假装是连接到此数据库的另一个数据库的本地用户。
		 */
		if (strchr(fc_port->user_name, '@') ==
			fc_port->user_name + strlen(fc_port->user_name) - 1)
			*strchr(fc_port->user_name, '@') = '\0';
		else
		{
			/* 追加 '@' 和 dbname */
			fc_port->user_name = psprintf("%s@%s", fc_port->user_name, fc_port->database_name);
		}
	}

	/*
	 * 将给定的数据库和用户名截断到 Postgres 名称的长度。
	 * 这避免了在给定过长名称时查找失败。
	 */
	if (strlen(fc_port->database_name) >= NAMEDATALEN)
		fc_port->database_name[NAMEDATALEN - 1] = '\0';
	if (strlen(fc_port->user_name) >= NAMEDATALEN)
		fc_port->user_name[NAMEDATALEN - 1] = '\0';

	if (am_walsender)
		MyBackendType = B_WAL_SENDER;
	else
		MyBackendType = B_BACKEND;

	/*
	 * 普通的 walsender 后端，例如用于流复制，并不
	 * 连接到特定的数据库。但是用于逻辑复制的 walsenders 需要
	 * 连接到特定的数据库。我们允许即使连接到某个数据库也可以发出
	 * 流复制命令，因为首先进行基本备份，然后从中流式传输更改是有意义的。
	 */
	if (am_walsender && !am_db_walsender)
		fc_port->database_name[0] = '\0';

	/*
	 * 完成在 TopMemoryContext 中放置内容。
	 */
	MemoryContextSwitchTo(fc_oldcontext);

	/*
	 * 如果由于数据库状态而拒绝连接，现在就说明，而不是在身份验证交换中浪费周期。
	 * （这也允许编写 pg_ping 实用程序。）
	 */
	switch (fc_port->canAcceptConnections)
	{
		case CAC_STARTUP:
			ereport(FATAL,
					(errcode(ERRCODE_CANNOT_CONNECT_NOW),
					 errmsg("the database system is starting up")));
			break;
		case CAC_NOTCONSISTENT:
			if (EnableHotStandby)
				ereport(FATAL,
						(errcode(ERRCODE_CANNOT_CONNECT_NOW),
						 errmsg("the database system is not yet accepting connections"),
						 errdetail("Consistent recovery state has not been yet reached.")));
			else
				ereport(FATAL,
						(errcode(ERRCODE_CANNOT_CONNECT_NOW),
						 errmsg("the database system is not accepting connections"),
						 errdetail("Hot standby mode is disabled.")));
			break;
		case CAC_SHUTDOWN:
			ereport(FATAL,
					(errcode(ERRCODE_CANNOT_CONNECT_NOW),
					 errmsg("the database system is shutting down")));
			break;
		case CAC_RECOVERY:
			ereport(FATAL,
					(errcode(ERRCODE_CANNOT_CONNECT_NOW),
					 errmsg("the database system is in recovery mode")));
			break;
		case CAC_TOOMANY:
			ereport(FATAL,
					(errcode(ERRCODE_TOO_MANY_CONNECTIONS),
					 errmsg("sorry, too many clients already")));
			break;
		case CAC_OK:
			break;
	}

	return STATUS_OK;
}

/*
 * 向客户端发送 NegotiateProtocolVersion。 这让客户端知道
 * 他们请求了一个比我们能够使用的新次要协议版本。
 * 我们将使用我们知道的最高版本；如果这是一个问题，客户端可以
 * 当然放弃连接。
 *
 * 我们还在响应中包含一个我们未理解的协议选项列表。
 * 这允许客户端包含在更新的协议版本或第三方协议
 * 扩展中可能存在的可选参数，而不必担心如果这些选项
 * 未被理解就必须重新连接，同时确保客户端意识到
 * 实际接受了哪些选项。
 */
static void fc_SendNegotiateProtocolVersion(List *fc_unrecognized_protocol_options)
{
	StringInfoData fc_buf;
	ListCell   *fc_lc;

	pq_beginmessage(&fc_buf, 'v'); /* NegotiateProtocolVersion */
	pq_sendint32(&fc_buf, PG_PROTOCOL_LATEST);
	pq_sendint32(&fc_buf, list_length(fc_unrecognized_protocol_options));
	foreach(fc_lc, fc_unrecognized_protocol_options)
		pq_sendstring(&fc_buf, lfirst(fc_lc));
	pq_endmessage(&fc_buf);

	/* 无需刷新，后面会有其他消息跟随 */
}

/*
 * 客户端已发送取消请求数据包，而不是正常的
 * 启动新连接的数据包。执行必要的处理。
 * 不会返回任何内容给客户端。
 */
static void fc_processCancelRequest(Port *fc_port, void *fc_pkt)
{
	CancelRequestPacket *fc_canc = (CancelRequestPacket *) fc_pkt;
	int			fc_backendPID;
	int32		fc_cancelAuthCode;
	Backend    *fc_bp;

#ifndef EXEC_BACKEND
	dlist_iter	fc_iter;
#else
	int			fc_i;
#endif

	fc_backendPID = (int) pg_ntoh32(fc_canc->backendPID);
	fc_cancelAuthCode = (int32) pg_ntoh32(fc_canc->cancelAuthCode);

	/*
	 * 查看我们是否有匹配的后端。在 EXEC_BACKEND 情况下，我们无法
	 * 再访问 postmaster 自身的后端列表，而必须依赖
	 * 共享内存中的重复数组。
	 */
#ifndef EXEC_BACKEND
	dlist_foreach(fc_iter, &BackendList)
	{
		fc_bp = dlist_container(Backend, elem, fc_iter.cur);
#else
	for (fc_i = MaxLivePostmasterChildren() - 1; fc_i >= 0; fc_i--)
	{
		fc_bp = (Backend *) &ShmemBackendArray[fc_i];
#endif
		if (fc_bp->pid == fc_backendPID)
		{
			if (fc_bp->cancel_key == fc_cancelAuthCode)
			{
				/* 找到匹配项；通知该后端取消当前操作 */
				ereport(DEBUG2,
						(errmsg_internal("processing cancel request: sending SIGINT to process %d",
										 fc_backendPID)));
				fc_signal_child(fc_bp->pid, SIGINT);
			}
			else
				/* 正确的 PID，错误的键：没办法，José */
				ereport(LOG,
						(errmsg("wrong key in cancel request for process %d",
								fc_backendPID)));
			return;
		}
#ifndef EXEC_BACKEND			/* 使 GNU Emacs 26.1 看到括号平衡 */
	}
#else
	}
#endif

	/* 没有匹配的后端 */
	ereport(LOG,
			(errmsg("PID %d in cancel request did not match any process",
					fc_backendPID)));
}

/*
 * canAcceptConnections --- 检查数据库状态是否允许特定类型的连接
 * backend_type 可以是 BACKEND_TYPE_NORMAL，
 * BACKEND_TYPE_AUTOVAC，或 BACKEND_TYPE_BGWORKER。
 * （请注意，我们尚不知道 NORMAL 连接是否可能会变成 walsender。）
 */
static CAC_state
fc_canAcceptConnections(int fc_backend_type)
{
	CAC_state	fc_result = CAC_OK;

	
/*
	 * 在启动/关闭/不一致的恢复状态下无法启动后端。
	 * 对于这个目的，我们将自动垃圾回收工作者与用户后端视为相同。
	 * 然而，bgworkers 被排除在此测试之外；我们期望 bgworker_should_start_now() 决定数据库状态是否允许它们。
	 */
	if (pmState != PM_RUN && pmState != PM_HOT_STANDBY &&
		fc_backend_type != BACKEND_TYPE_BGWORKER)
	{
		if (Shutdown > NoShutdown)
			return CAC_SHUTDOWN;	/* 正在等待关闭 */
		else if (!FatalError && pmState == PM_STARTUP)
			return CAC_STARTUP; /* 常规启动 */
		else if (!FatalError && pmState == PM_RECOVERY)
			return CAC_NOTCONSISTENT;	/* 尚未达到一致的恢复状态 */
		else
			return CAC_RECOVERY;	/* 否则必须进行崩溃恢复 */
	}

	/*
	 * "智能关闭"限制仅适用于常规连接，
	 * 不适用于自动真空工作进程或后台工作进程。
	 */
	if (!connsAllowed && fc_backend_type == BACKEND_TYPE_NORMAL)
		return CAC_SHUTDOWN;	/* 正在等待关闭 */

	/*
	 * 不要启动过多的子进程。
	 *
	 * 在这里，我们允许的连接数多于可以拥有的后端数量，因为某些连接
	 * 可能仍在进行身份验证；它们可能身份验证失败，或者某些现有的
	 * 后端可能在身份验证周期完成之前退出。精确的
	 * MaxBackends 限制在新后端尝试加入
	 * shared-inval 后端数组时强制执行。
	 *
	 * 此处限制必须与每个子进程数组的大小匹配；
	 * 请参阅 MaxLivePostmasterChildren() 的注释。
	 */
	if (fc_CountChildren(BACKEND_TYPE_ALL) >= MaxLivePostmasterChildren())
		fc_result = CAC_TOOMANY;

	return fc_result;
}


/*
 * ConnCreate -- 创建一个本地连接数据结构
 *
 * 失败时返回 NULL，除了内存不足这是致命的。
 */
static Port *
fc_ConnCreate(int fc_serverFd)
{
	Port	   *fc_port;

	if (!(fc_port = (Port *) calloc(1, sizeof(Port))))
	{
		ereport(LOG,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("out of memory")));
		fc_ExitPostmaster(1);
	}

	if (StreamConnection(fc_serverFd, fc_port) != STATUS_OK)
	{
		if (fc_port->sock != PGINVALID_SOCKET)
			StreamClose(fc_port->sock);
		fc_ConnFree(fc_port);
		return NULL;
	}

	return fc_port;
}


/*
 * ConnFree -- 释放一个本地连接数据结构
 *
 * 调用者已经关闭了套接字（如果有的话），因此这里没有太多
 * 要做的事情。
 */
static void fc_ConnFree(Port *fc_conn)
{
	free(fc_conn);
}


/*
 * ClosePostmasterPorts -- 关闭所有 postmaster 的开放套接字
 *
 * 这在子进程启动期间调用，以释放该子进程不需要的文件描述符。
 * 当然，postmaster 仍然打开它们。
 *
 * 注意：我们将 am_syslogger 作为布尔值传递，因为在调用时
 * 我们不想设置全局变量。
 */
void ClosePostmasterPorts(bool fc_am_syslogger)
{
	int			fc_i;

#ifndef WIN32

	/*
	 * 关闭 postmaster 死亡监视管道的写入端。这一点非常重要，
	 * 因此如果 postmaster 死亡，其他进程不会因为我们保持管道打开
	 * 而认为它仍在运行。
	 */
	if (close(postmaster_alive_fds[POSTMASTER_FD_OWN]) != 0)
		ereport(FATAL,
				(errcode_for_file_access(),
				 errmsg_internal("could not close postmaster death monitoring pipe in child process: %m")));
	postmaster_alive_fds[POSTMASTER_FD_OWN] = -1;
	/* 通知 fd.c 我们释放了一个管道 FD。 */
	ReleaseExternalFD();
#endif

	/*
	 * 关闭 postmaster 的监听套接字。这些不被 fd.c 跟踪，
	 * 所以我们在这里不调用 ReleaseExternalFD()。
	 */
	for (fc_i = 0; fc_i < MAXLISTEN; fc_i++)
	{
		if (ListenSocket[fc_i] != PGINVALID_SOCKET)
		{
			StreamClose(ListenSocket[fc_i]);
			ListenSocket[fc_i] = PGINVALID_SOCKET;
		}
	}

	/*
	 * 如果使用 syslogger，关闭管道的读端。我们也不
	 * 在 fd.c 中跟踪这个。
	 */
	if (!fc_am_syslogger)
	{
#ifndef WIN32
		if (syslogPipe[0] >= 0)
			close(syslogPipe[0]);
		syslogPipe[0] = -1;
#else
		if (syslogPipe[0])
			CloseHandle(syslogPipe[0]);
		syslogPipe[0] = 0;
#endif
	}

#ifdef USE_BONJOUR
	/* 如果使用 Bonjour，关闭与 mDNS 守护进程的连接 */
	if (bonjour_sdref)
		close(DNSServiceRefSockFD(bonjour_sdref));
#endif
}


/*
 * InitProcessGlobals -- 设置 MyStartTime[stamp]，随机种子
 *
 * 在 postmaster 和每个后端的早期调用。
 */
void InitProcessGlobals(void)
{
	MyStartTimestamp = GetCurrentTimestamp();
	MyStartTime = timestamptz_to_time_t(MyStartTimestamp);

	/*
	 * 在每个进程中设置不同的全局种子。我们希望得到一些
	 * 无法预测的东西，因此如果可能，使用高质量随机位作为
	 * 种子。否则，退回到基于时间戳和 PID 的种子。
	 */
	if (unlikely(!pg_prng_strong_seed(&pg_global_prng_state)))
	{
		uint64		fc_rseed;

		/*
		 * 由于 PID 和时间戳在其最不重要的位中变更更频繁，
		 * 将时间戳左移以允许在给定时间段内使用更大的种子总数量。
		 * 由于这将仅留下每 ~1 秒循环的时间戳的 20 位，
		 * 还需要混合一些高位。
		 */
		fc_rseed = ((uint64) MyProcPid) ^
			((uint64) MyStartTimestamp << 12) ^
			((uint64) MyStartTimestamp >> 20);

		pg_prng_seed(&pg_global_prng_state, fc_rseed);
	}

	/*
	 * 还要确保我们为 random(3) 设置了良好的种子。该
	 * 种子在核心 Postgres 中已被弃用，但扩展可能会使用它。
	 */
#ifndef WIN32
	srandom(pg_prng_uint32(&pg_global_prng_state));
#endif
}


/*
 * reset_shared -- 重置共享内存和信号量
 */
static void fc_reset_shared(void)
{
	/*
	 * 创建或重新创建共享内存和信号量。
	 *
	 * 注意：在每个“生命周期周期”中，我们通常会分配相同的 IPC 键
	 * （如果使用 SysV 共享内存和/或信号量）。这有助于确保我们能够
	 * 清除死掉的 IPC 对象，如果 postmaster 崩溃并重新启动。
	 */
	CreateSharedMemoryAndSemaphores();
}


/*
 * SIGHUP -- 重新读取配置文件，并告诉子进程也这么做
 */
static void fc_SIGHUP_handler(SIGNAL_ARGS)
{
	int			fc_save_errno = errno;

	/*
	 * 我们依赖信号机制来阻塞所有信号……除了
	 * Windows，它缺少 sigaction()，所以我们必须手动处理。
	 */
#ifdef WIN32
	PG_SETMASK(&BlockSig);
#endif

	if (Shutdown <= SmartShutdown)
	{
		ereport(LOG,
				(errmsg("received SIGHUP, reloading configuration files")));
#ifdef FDD
		if(NULL != fdb_write_audit_hook)
		{
			fdb_write_audit_hook("received SIGHUP and reloading configuration files");
		}
#endif //FDD
		ProcessConfigFile(PGC_SIGHUP);
		SignalChildren(SIGHUP);
		if (StartupPID != 0)
			fc_signal_child(StartupPID, SIGHUP);
		if (BgWriterPID != 0)
			fc_signal_child(BgWriterPID, SIGHUP);
		if (CheckpointerPID != 0)
			fc_signal_child(CheckpointerPID, SIGHUP);
		if (WalWriterPID != 0)
			fc_signal_child(WalWriterPID, SIGHUP);
		if (WalReceiverPID != 0)
			fc_signal_child(WalReceiverPID, SIGHUP);
		if (AutoVacPID != 0)
			fc_signal_child(AutoVacPID, SIGHUP);
		if (PgArchPID != 0)
			fc_signal_child(PgArchPID, SIGHUP);
		if (SysLoggerPID != 0)
			fc_signal_child(SysLoggerPID, SIGHUP);

		/* 也重新加载身份验证配置文件 */
		if (!load_hba())
			ereport(LOG,
			/* 翻译者：%s 是一个配置文件 */
					(errmsg("%s was not reloaded", "pg_hba.conf")));

		if (!load_ident())
			ereport(LOG,
					(errmsg("%s was not reloaded", "pg_ident.conf")));

#ifdef USE_SSL
		/* 还重新加载 SSL 配置 */
		if (EnableSSL)
		{
			if (secure_initialize(false) == 0)
				LoadedSSL = true;
			else
				ereport(LOG,
						(errmsg("SSL configuration was not reloaded")));
		}
		else
		{
			secure_destroy();
			LoadedSSL = false;
		}
#endif

#ifdef EXEC_BACKEND
		/* 更新未来子进程的起始点文件 */
		write_nondefault_variables(PGC_SIGHUP);
#endif
	}

#ifdef WIN32
	PG_SETMASK(&UnBlockSig);
#endif

	errno = fc_save_errno;
}


/*
 * pmdie -- 处理各种 postmaster 信号的信号处理程序。
 */
static void fc_pmdie(SIGNAL_ARGS)
{
	int			fc_save_errno = errno;

	/*
	 * 我们依赖信号机制来阻塞所有信号……除了
	 * Windows，它缺少 sigaction()，所以我们必须手动处理。
	 */
#ifdef WIN32
	PG_SETMASK(&BlockSig);
#endif

	ereport(DEBUG2,
			(errmsg_internal("postmaster received signal %d",
							 postgres_signal_arg)));

	switch (postgres_signal_arg)
	{
		case SIGTERM:

			/*
			 * 智能关闭：
			 *
			 * 等待子进程完成工作，然后关闭。
			 */
			if (Shutdown >= SmartShutdown)
				break;
			Shutdown = SmartShutdown;
			ereport(LOG,
					(errmsg("received smart shutdown request")));

			/* 报告状态 */
			AddToDataDirLockFile(LOCK_FILE_LINE_PM_STATUS, PM_STATUS_STOPPING);
#ifdef USE_SYSTEMD
			sd_notify(0, "STOPPING=1");
#endif

			/*
			 * 如果我们达到了正常运行状态，我们将直接等待
			 * 客户端后端退出。如果已经在 PM_STOP_BACKENDS 或进一步状态，
			 * 则不更改它。
			 */
			if (pmState == PM_RUN || pmState == PM_HOT_STANDBY)
				connsAllowed = false;
			else if (pmState == PM_STARTUP || pmState == PM_RECOVERY)
			{
				/* 应该没有客户端，因此继续停止子进程 */
				pmState = PM_STOP_BACKENDS;
			}

			/*
			 * 现在等待在线备份模式结束和后端退出。如果情况已经是这样，PostmasterStateMachine将采取下一步。
			 */
			fc_PostmasterStateMachine();
			break;

		case SIGINT:

			/*
			 * 快速关闭：
			 *
			 * 使用SIGTERM中止所有子进程（回滚活动事务并退出），并在它们消失时关闭。
			 */
			if (Shutdown >= FastShutdown)
				break;
			Shutdown = FastShutdown;
			ereport(LOG,
					(errmsg("received fast shutdown request")));

			/* 报告状态 */
			AddToDataDirLockFile(LOCK_FILE_LINE_PM_STATUS, PM_STATUS_STOPPING);
#ifdef USE_SYSTEMD
			sd_notify(0, "STOPPING=1");
#endif

			if (pmState == PM_STARTUP || pmState == PM_RECOVERY)
			{
				/* 只需静默关闭后台进程 */
				pmState = PM_STOP_BACKENDS;
			}
			else if (pmState == PM_RUN ||
					 pmState == PM_HOT_STANDBY)
			{
				/* 报告我们即将结束活动客户端会话 */
				ereport(LOG,
						(errmsg("aborting any active transactions")));
				pmState = PM_STOP_BACKENDS;
			}

			/*
			 * PostmasterStateMachine将发出任何必要的信号，或者如果没有子进程需要被杀死，采取下一步。
			 */
			fc_PostmasterStateMachine();
			break;

		case SIGQUIT:

			/*
			 * 立即关闭：
			 *
			 * 使用SIGQUIT中止所有子进程，等待它们退出，使用SIGKILL终止剩余的进程，然后退出，而不尝试正确关闭数据库系统。
			 */
			if (Shutdown >= ImmediateShutdown)
				break;
			Shutdown = ImmediateShutdown;
			ereport(LOG,
					(errmsg("received immediate shutdown request")));

			/* 报告状态 */
			AddToDataDirLockFile(LOCK_FILE_LINE_PM_STATUS, PM_STATUS_STOPPING);
#ifdef USE_SYSTEMD
			sd_notify(0, "STOPPING=1");
#endif

			/* 告诉子进程尽快关闭 */
			SetQuitSignalReason(PMQUIT_FOR_STOP);
			fc_TerminateChildren(SIGQUIT);
			pmState = PM_WAIT_BACKENDS;

			/* 为它们的死亡设置计时器 */
			AbortStartTime = time(NULL);

			/*
			 * 现在等待后端退出。如果没有，PostmasterStateMachine将采取下一步。
			 */
			fc_PostmasterStateMachine();
			break;
	}

#ifdef WIN32
	PG_SETMASK(&UnBlockSig);
#endif

	errno = fc_save_errno;
}

/*
 * 收割者 -- 在子进程死亡后进行清理的信号处理程序。
 */
static void fc_reaper(SIGNAL_ARGS)
{
	int			fc_save_errno = errno;
	int			fc_pid;			/* 死亡子进程的进程ID */
	int			fc_exitstatus;		/* 它的退出状态 */

	/*
	 * 我们依赖信号机制来阻塞所有信号……除了
	 * Windows，它缺少 sigaction()，所以我们必须手动处理。
	 */
#ifdef WIN32
	PG_SETMASK(&BlockSig);
#endif

	ereport(DEBUG4,
			(errmsg_internal("reaping dead processes")));

	while ((fc_pid = waitpid(-1, &fc_exitstatus, WNOHANG)) > 0)
	{
		/*
		 * 检查该子进程是否为启动进程。
		 */
		if (fc_pid == StartupPID)
		{
			StartupPID = 0;

			/*
			 * 启动进程响应关闭请求退出（或者它无论如何正常完成）。
			 */
			if (Shutdown > NoShutdown &&
				(EXIT_STATUS_0(fc_exitstatus) || EXIT_STATUS_1(fc_exitstatus)))
			{
				StartupStatus = STARTUP_NOT_RUNNING;
				pmState = PM_WAIT_BACKENDS;
				/* PostmasterStateMachine逻辑会处理其余部分 */
				continue;
			}

			if (EXIT_STATUS_3(fc_exitstatus))
			{
				ereport(LOG,
						(errmsg("shutdown at recovery target")));
				StartupStatus = STARTUP_NOT_RUNNING;
				Shutdown = Max(Shutdown, SmartShutdown);
				fc_TerminateChildren(SIGTERM);
				pmState = PM_WAIT_BACKENDS;
				/* PostmasterStateMachine逻辑会处理其余部分 */
				continue;
			}

			/*
			 * 启动进程在PM_STARTUP期间的意外退出（包括FATAL退出）被视为灾难性。在此时没有其他进程在运行，因此我们可以直接退出。
			 */
			if (pmState == PM_STARTUP &&
				StartupStatus != STARTUP_SIGNALED &&
				!EXIT_STATUS_0(fc_exitstatus))
			{
				fc_LogChildExit(LOG, _("startup process"),
							 fc_pid, fc_exitstatus);
				ereport(LOG,
						(errmsg("aborting startup due to startup process failure")));
				fc_ExitPostmaster(1);
			}

			/*
			 * 在PM_STARTUP之后，启动进程的任何意外退出（包括FATAL退出）都是灾难性的，因此杀死其他子进程，并设置StartupStatus，以便我们在它们消失后不再尝试重新初始化。例外情况：如果StartupStatus是STARTUP_SIGNALED，则我们之前已向启动进程发送了SIGQUIT；所以这可能就是它死亡的原因，在这种情况下我们确实希望尝试重新启动。
			 *
			 * 这一部分还处理了我们在PM_STARTUP期间由于某个dead_end子进程崩溃而发送SIGQUIT的情况：在这种情况下，如果启动进程在SIGQUIT上死亡，我们需要过渡到PM_WAIT_BACKENDS状态，这将允许PostmasterStateMachine重新启动启动进程。（另一方面，如果启动进程正常完成，而我们对SIGQUIT反应过慢，则会继续并开始正常操作。）
			 */
			if (!EXIT_STATUS_0(fc_exitstatus))
			{
				if (StartupStatus == STARTUP_SIGNALED)
				{
					StartupStatus = STARTUP_NOT_RUNNING;
					if (pmState == PM_STARTUP)
						pmState = PM_WAIT_BACKENDS;
				}
				else
					StartupStatus = STARTUP_CRASHED;
				fc_HandleChildCrash(fc_pid, fc_exitstatus,
								 _("startup process"));
				continue;
			}

			/*
			 * 启动成功，开始正常操作
			 */
			StartupStatus = STARTUP_NOT_RUNNING;
			FatalError = false;
			AbortStartTime = 0;
			ReachedNormalRunning = true;
			pmState = PM_RUN;
			connsAllowed = true;

			/*
			 * 启动后台任务，如果在进入一致性恢复状态时还没有这样做。即使这失败了也没关系，我们稍后会再尝试。
			 */
			if (CheckpointerPID == 0)
				CheckpointerPID = StartCheckpointer();
			if (BgWriterPID == 0)
				BgWriterPID = StartBackgroundWriter();
			if (WalWriterPID == 0)
				WalWriterPID = StartWalWriter();

			/*
			 * 同样，根据需要启动其他特殊子进程。在重启情况下，其中一些可能已经存活。
			 */
			if (!IsBinaryUpgrade && AutoVacuumingActive() && AutoVacPID == 0)
				AutoVacPID = StartAutoVacLauncher();
			if (PgArchStartupAllowed() && PgArchPID == 0)
				PgArchPID = StartArchiver();

			/* 工作进程现在可能被调度启动 */
			fc_maybe_start_bgworkers();

			/* 在这一点上我们真的可以开始业务了 */
			ereport(LOG,
					(errmsg("database system is ready to accept connections")));
#ifdef FDD
			if(NULL != fdb_write_audit_hook)
			{
				fdb_write_audit_hook("database system is ready to accept connections");
			}
#endif //FDD
			/* 报告状态 */
			AddToDataDirLockFile(LOCK_FILE_LINE_PM_STATUS, PM_STATUS_READY);
#ifdef USE_SYSTEMD
			sd_notify(0, "READY=1");
#endif

			continue;
		}

		/*
		 * 是bgwriter吗？正常退出可以忽略；如果需要，我们将在postmaster主循环的下一次迭代中启动一个新的。任何其他退出条件都被视为崩溃。
		 */
		if (fc_pid == BgWriterPID)
		{
			BgWriterPID = 0;
			if (!EXIT_STATUS_0(fc_exitstatus))
				fc_HandleChildCrash(fc_pid, fc_exitstatus,
								 _("background writer process"));
			continue;
		}

		/*
		 * 是检查点进程吗？
		 */
		if (fc_pid == CheckpointerPID)
		{
			CheckpointerPID = 0;
			if (EXIT_STATUS_0(fc_exitstatus) && pmState == PM_SHUTDOWN)
			{
				/*
				 * 好吧，我们看到在被告知关闭后，检查点进程正常退出。我们期待它写入一个关闭检查点。（如果某种原因它没有，恢复将在下次postmaster启动时发生。）
				 *
				 * 此时我们应该没有正常的后端子进程（否则我们不会处于PM_SHUTDOWN状态），但我们可能有死亡子进程需要等待。
				 *
				 * 如果我们有一个归档进程，请告诉它做最后一次归档并退出。同样，如果我们有walsender进程，请告诉它们发送任何剩余的WAL并退出。
				 */
				Assert(Shutdown > NoShutdown);

				/* 最后一次唤醒归档进程 */
				if (PgArchPID != 0)
					fc_signal_child(PgArchPID, SIGUSR2);

				/*
				 * 最后一次唤醒walsender。目前不应该有正常的后端进程存在。
				 */
				SignalChildren(SIGUSR2);

				pmState = PM_SHUTDOWN_2;
			}
			else
			{
				/*
				 * 检查点进程的任何意外退出（包括FATAL退出）都被视为崩溃。
				 */
				fc_HandleChildCrash(fc_pid, fc_exitstatus,
								 _("checkpointer process"));
			}

			continue;
		}

		/*
		 * 是WAL写入进程吗？正常退出可以忽略；如果需要，我们将在postmaster主循环的下一次迭代中启动一个新的。任何其他退出条件都被视为崩溃。
		 */
		if (fc_pid == WalWriterPID)
		{
			WalWriterPID = 0;
			if (!EXIT_STATUS_0(fc_exitstatus))
				fc_HandleChildCrash(fc_pid, fc_exitstatus,
								 _("WAL writer process"));
			continue;
		}

		/*
		 * 是 wal 接收器吗？如果退出状态为零（正常）或一（致命退出），
		 * 我们假设一切正常，就像正常后端一样。
		 * （如果我们需要一个新的 wal 接收器，我们将在 postmaster 主循环的下一次迭代中启动一个。）
		 */
		if (fc_pid == WalReceiverPID)
		{
			WalReceiverPID = 0;
			if (!EXIT_STATUS_0(fc_exitstatus) && !EXIT_STATUS_1(fc_exitstatus))
				fc_HandleChildCrash(fc_pid, fc_exitstatus,
								 _("WAL receiver process"));
			continue;
		}

		/*
		 * 是自动清理启动器吗？正常退出可以忽略；如果有必要，我们将在
		 * 主进程的主循环的下一次迭代中启动一个新的。任何其他退出条件都
		 * 被视为崩溃。
		 */
		if (fc_pid == AutoVacPID)
		{
			AutoVacPID = 0;
			if (!EXIT_STATUS_0(fc_exitstatus))
				fc_HandleChildCrash(fc_pid, fc_exitstatus,
								 _("autovacuum launcher process"));
			continue;
		}

		/*
		 * 是归档器吗？如果退出状态为零（正常）或一（致命退出），我们就
		 * 假设一切都好，就像正常的后端一样，尝试重新启动一个新的，这样
		 * 我们可以立即重试归档剩余的文件。（如果失败，下一轮主进程
		 * 循环时我们会再试。）除非我们在等待它关闭；在这种情况下不
		 * 重新启动它，PostmasterStateMachine() 将推进到下一个关闭步骤。
		 */
		if (fc_pid == PgArchPID)
		{
			PgArchPID = 0;
			if (!EXIT_STATUS_0(fc_exitstatus) && !EXIT_STATUS_1(fc_exitstatus))
				fc_HandleChildCrash(fc_pid, fc_exitstatus,
								 _("archiver process"));
			if (PgArchStartupAllowed())
				PgArchPID = StartArchiver();
			continue;
		}

		/* 是系统日志记录器吗？如果是，请尝试启动一个新的 */
		if (fc_pid == SysLoggerPID)
		{
			SysLoggerPID = 0;
			/* 为了安全起见，*先* 启动新的日志记录器 */
			SysLoggerPID = SysLogger_Start();
			if (!EXIT_STATUS_0(fc_exitstatus))
				fc_LogChildExit(LOG, _("system logger process"),
							 fc_pid, fc_exitstatus);
			continue;
		}

		/* 是我们的某个后台工作者吗？ */
		if (fc_CleanupBackgroundWorker(fc_pid, fc_exitstatus))
		{
			/* 让它重启 */
			HaveCrashedWorker = true;
			continue;
		}

		/*
		 * 否则执行标准后端子进程清理。
		 */
		fc_CleanupBackend(fc_pid, fc_exitstatus);
	}							/* 遍历待处理的子进程死亡报告 */

	/*
	 * 清空 SIGCHLD 队列后，查看是否有状态变化
	 * 或需要进行的操作。
	 */
	fc_PostmasterStateMachine();

	/* 完成信号处理程序 */
#ifdef WIN32
	PG_SETMASK(&UnBlockSig);
#endif

	errno = fc_save_errno;
}

/*
 * 扫描 bgworkers 列表，查看给定的 PID（刚刚停止
 * 或崩溃的）是否在其中。如果是，则处理其关闭并返回
 * true。如果不是后台工作者，返回 false。
 *
 * 这在很大程度上是基于 CleanupBackend。一个重要的区别是
 * 我们还不知道正在死亡的进程是一个后台工作者，因此
 * 在确认之前必须保持沉默。
 */
static bool fc_CleanupBackgroundWorker(int fc_pid,
						int fc_exitstatus) /* 子进程的退出状态 */
{
	char		fc_namebuf[MAXPGPATH];
	slist_mutable_iter fc_iter;

	slist_foreach_modify(fc_iter, &BackgroundWorkerList)
	{
		RegisteredBgWorker *fc_rw;

		fc_rw = slist_container(RegisteredBgWorker, rw_lnode, fc_iter.cur);

		if (fc_rw->rw_pid != fc_pid)
			continue;

#ifdef WIN32
		/* 请参阅 CleanupBackend */
		if (exitstatus == ERROR_WAIT_NO_CHILDREN)
			exitstatus = 0;
#endif

		snprintf(fc_namebuf, MAXPGPATH, _("background worker \"%s\""),
				 fc_rw->rw_worker.bgw_type);


		if (!EXIT_STATUS_0(fc_exitstatus))
		{
			/* 记录时间戳，以便我们知道何时重启工作者。 */
			fc_rw->rw_crashed_at = GetCurrentTimestamp();
		}
		else
		{
			/* 退出状态为零意味着终止 */
			fc_rw->rw_crashed_at = 0;
			fc_rw->rw_terminate = true;
		}

		/*
		 * 此外，就像一个后端一样，任何退出状态不是 0 或
		 * 1 的情况都被视为崩溃，并导致系统范围重启。
		 */
		if (!EXIT_STATUS_0(fc_exitstatus) && !EXIT_STATUS_1(fc_exitstatus))
		{
			fc_HandleChildCrash(fc_pid, fc_exitstatus, fc_namebuf);
			return true;
		}

		/*
		 * 我们必须释放主进程子进程插槽。如果工作者未能
		 * 这样做，它没有清理自己，导致崩溃重启
		 * 循环。
		 */
		if (!ReleasePostmasterChildSlot(fc_rw->rw_child_slot))
		{
			fc_HandleChildCrash(fc_pid, fc_exitstatus, fc_namebuf);
			return true;
		}

		/* 从 BackendList 中移除，并清除剩余数据 */
		dlist_delete(&fc_rw->rw_backend->elem);
#ifdef EXEC_BACKEND
		fc_ShmemBackendArrayRemove(fc_rw->rw_backend);
#endif

		/*
		 * 这种后台工作者很可能启动了其他后台工作者，并请求
		 * 在该工作者启动或停止时进行通知。如果是，
		 * 取消任何发往现在死掉的后端的通知。
		 */
		if (fc_rw->rw_backend->bgworker_notify)
			BackgroundWorkerStopNotifications(fc_rw->rw_pid);
		free(fc_rw->rw_backend);
		fc_rw->rw_backend = NULL;
		fc_rw->rw_pid = 0;
		fc_rw->rw_child_slot = 0;
		ReportBackgroundWorkerExit(&fc_iter);	/* 报告子进程死亡 */

		fc_LogChildExit(EXIT_STATUS_0(fc_exitstatus) ? DEBUG1 : LOG,
					 fc_namebuf, fc_pid, fc_exitstatus);

		return true;
	}

	return false;
}

/*
 * CleanupBackend -- 清理已终止的后端。
 *
 * 移除与后端关联的所有本地状态。
 *
 * 如果你更改了这个，请参见 CleanupBackgroundWorker。
 */
static void fc_CleanupBackend(int fc_pid,
			   int fc_exitstatus)	/* 子进程的退出状态。 */
{
	dlist_mutable_iter fc_iter;

	fc_LogChildExit(DEBUG2, _("server process"), fc_pid, fc_exitstatus);

	/*
	 * 如果一个后端以较为丑陋的方式崩溃，则我们必须向
	 * 所有其他后端发信号快速死亡。如果退出状态为零
	 * （正常）或一（致命退出），我们假设一切良好
	 * 并继续从活动后端列表中移除该后端。
	 */

#ifdef WIN32

	/*
	 * 在 win32 上，也将 ERROR_WAIT_NO_CHILDREN（128）视为非致命情况，
	 * 因为在负载下有时发生这个问题，当进程无法
	 * 正常启动时（在开始使用共享内存之前很久）。微软报告说
	 * 这与互斥体失败有关：
	 * http://archives.postgresql.org/pgsql-hackers/2010-09/msg00790.php
	 */
	if (exitstatus == ERROR_WAIT_NO_CHILDREN)
	{
		LogChildExit(LOG, _("server process"), pid, exitstatus);
		exitstatus = 0;
	}
#endif

	if (!EXIT_STATUS_0(fc_exitstatus) && !EXIT_STATUS_1(fc_exitstatus))
	{
		fc_HandleChildCrash(fc_pid, fc_exitstatus, _("server process"));
		return;
	}

	dlist_foreach_modify(fc_iter, &BackendList)
	{
		Backend    *fc_bp = dlist_container(Backend, elem, fc_iter.cur);

		if (fc_bp->pid == fc_pid)
		{
			if (!fc_bp->dead_end)
			{
				if (!ReleasePostmasterChildSlot(fc_bp->child_slot))
				{
					/*
					 * 哦，子进程未能自行清理。最终
					 * 被视为崩溃。
					 */
					fc_HandleChildCrash(fc_pid, fc_exitstatus, _("server process"));
					return;
				}
#ifdef EXEC_BACKEND
				fc_ShmemBackendArrayRemove(fc_bp);
#endif
			}
			if (fc_bp->bgworker_notify)
			{
				/*
				 * 这个后端可能被指派在某个后台工作者启动或停止时
				 * 接收 SIGUSR1。取消这些通知，因为我们不想给
				 * 不是 PostgreSQL 后端的 PID 发信号。这个在（可能
				 * 非常常见）后端从未请求过任何此类通知的情况下被跳过。
				 */
				BackgroundWorkerStopNotifications(fc_bp->pid);
			}
			dlist_delete(fc_iter.cur);
			free(fc_bp);
			break;
		}
	}
}

/*
 * HandleChildCrash -- 清理失败的后端、bgwriter、检查点、walwriter、
 * 自动清理、归档器或后台工作者后。
 *
 * 这里的目标是清理关于子进程的本地状态，并向所有
 * 其他剩余子进程发信号快速死亡。
 */
static void fc_HandleChildCrash(int fc_pid, int fc_exitstatus, const char *fc_procname)
{
	dlist_mutable_iter fc_iter;
	slist_iter	fc_siter;
	Backend    *fc_bp;
	bool		fc_take_action;

	/*
	 * 仅在这是第一次进程崩溃且我们没有进行即时关闭时
	 * 才记录消息和发送信号；否则，我们只是来更新
	 * 主进程对活动进程的看法。如果我们已经向子进程发送
	 * 了信号，则期望非零退出状态，因此不要占用日志。
	 */
	fc_take_action = !FatalError && Shutdown != ImmediateShutdown;

	if (fc_take_action)
	{
		fc_LogChildExit(LOG, fc_procname, fc_pid, fc_exitstatus);
		ereport(LOG,
				(errmsg("terminating any other active server processes")));
		SetQuitSignalReason(PMQUIT_FOR_CRASH);
	}

	/* 处理后台工作者。 */
	slist_foreach(fc_siter, &BackgroundWorkerList)
	{
		RegisteredBgWorker *fc_rw;

		fc_rw = slist_container(RegisteredBgWorker, rw_lnode, fc_siter.cur);
		if (fc_rw->rw_pid == 0)
			continue;			/* 不在运行 */
		if (fc_rw->rw_pid == fc_pid)
		{
			/*
			 * 找到了刚刚死亡的工作进程，所以将其移除。
			 */
			(void) ReleasePostmasterChildSlot(fc_rw->rw_child_slot);
			dlist_delete(&fc_rw->rw_backend->elem);
#ifdef EXEC_BACKEND
			fc_ShmemBackendArrayRemove(fc_rw->rw_backend);
#endif
			free(fc_rw->rw_backend);
			fc_rw->rw_backend = NULL;
			fc_rw->rw_pid = 0;
			fc_rw->rw_child_slot = 0;
			/* 不要重置 crashed_at */
			/* 也不要报告子进程停止 */
			/* 保持循环，这样我们可以向剩余的工作进程发送信号 */
		}
		else
		{
			/*
			 * 这个工作进程仍然存活。除非我们已经这样做过，否则通知它
			 * 自杀。
			 *
			 * SIGQUIT 是一个特殊信号，表示退出而不调用 proc_exit
			 * 并让用户知道发生了什么。但如果设置了 SendStop
			 * （在命令行上使用 -T），那么我们发送 SIGSTOP，而不是，
			 * 这样我们可以手动从所有后台进程获取核心转储。
			 */
			if (fc_take_action)
			{
				ereport(DEBUG2,
						(errmsg_internal("sending %s to process %d",
										 (SendStop ? "SIGSTOP" : "SIGQUIT"),
										 (int) fc_rw->rw_pid)));
				fc_signal_child(fc_rw->rw_pid, (SendStop ? SIGSTOP : SIGQUIT));
			}
		}
	}

	/* 处理常规后台进程 */
	dlist_foreach_modify(fc_iter, &BackendList)
	{
		fc_bp = dlist_container(Backend, elem, fc_iter.cur);

		if (fc_bp->pid == fc_pid)
		{
			/*
			 * 找到了刚刚死亡的后台进程，所以将其移除。
			 */
			if (!fc_bp->dead_end)
			{
				(void) ReleasePostmasterChildSlot(fc_bp->child_slot);
#ifdef EXEC_BACKEND
				fc_ShmemBackendArrayRemove(fc_bp);
#endif
			}
			dlist_delete(fc_iter.cur);
			free(fc_bp);
			/* 保持循环，这样我们可以向剩余的后台进程发送信号 */
		}
		else
		{
			/*
			 * 这个后台进程仍然存活。除非我们已经这样做过，否则通知它
			 * 自杀。
			 *
			 * SIGQUIT 是一个特殊信号，表示退出而不调用 proc_exit
			 * 并让用户知道发生了什么。但如果设置了 SendStop
			 * （在命令行上使用 -T），那么我们发送 SIGSTOP，而不是，
			 * 这样我们可以手动从所有后台进程获取核心转储。
			 *
			 * 我们可以在这里排除已死亡的子进程，但至少在
			 * SIGSTOP 的情况下，包括它们似乎更好。
			 *
			 * 后台工作进程已经在上面处理过；在这里忽略它们。
			 */
			if (fc_bp->bkend_type == BACKEND_TYPE_BGWORKER)
				continue;

			if (fc_take_action)
			{
				ereport(DEBUG2,
						(errmsg_internal("sending %s to process %d",
										 (SendStop ? "SIGSTOP" : "SIGQUIT"),
										 (int) fc_bp->pid)));
				fc_signal_child(fc_bp->pid, (SendStop ? SIGSTOP : SIGQUIT));
			}
		}
	}

	/* 也处理启动进程 */
	if (fc_pid == StartupPID)
	{
		StartupPID = 0;
		/* 调用者调整 StartupStatus，所以在这里不要触碰它 */
	}
	else if (StartupPID != 0 && fc_take_action)
	{
		ereport(DEBUG2,
				(errmsg_internal("sending %s to process %d",
								 (SendStop ? "SIGSTOP" : "SIGQUIT"),
								 (int) StartupPID)));
		fc_signal_child(StartupPID, (SendStop ? SIGSTOP : SIGQUIT));
		StartupStatus = STARTUP_SIGNALED;
	}

	/* 也处理 bgwriter */
	if (fc_pid == BgWriterPID)
		BgWriterPID = 0;
	else if (BgWriterPID != 0 && fc_take_action)
	{
		ereport(DEBUG2,
				(errmsg_internal("sending %s to process %d",
								 (SendStop ? "SIGSTOP" : "SIGQUIT"),
								 (int) BgWriterPID)));
		fc_signal_child(BgWriterPID, (SendStop ? SIGSTOP : SIGQUIT));
	}

	/* 也处理检查点进程 */
	if (fc_pid == CheckpointerPID)
		CheckpointerPID = 0;
	else if (CheckpointerPID != 0 && fc_take_action)
	{
		ereport(DEBUG2,
				(errmsg_internal("sending %s to process %d",
								 (SendStop ? "SIGSTOP" : "SIGQUIT"),
								 (int) CheckpointerPID)));
		fc_signal_child(CheckpointerPID, (SendStop ? SIGSTOP : SIGQUIT));
	}

	/* 也处理 walwriter */
	if (fc_pid == WalWriterPID)
		WalWriterPID = 0;
	else if (WalWriterPID != 0 && fc_take_action)
	{
		ereport(DEBUG2,
				(errmsg_internal("sending %s to process %d",
								 (SendStop ? "SIGSTOP" : "SIGQUIT"),
								 (int) WalWriterPID)));
		fc_signal_child(WalWriterPID, (SendStop ? SIGSTOP : SIGQUIT));
	}

	/* 也处理 walreceiver */
	if (fc_pid == WalReceiverPID)
		WalReceiverPID = 0;
	else if (WalReceiverPID != 0 && fc_take_action)
	{
		ereport(DEBUG2,
				(errmsg_internal("sending %s to process %d",
								 (SendStop ? "SIGSTOP" : "SIGQUIT"),
								 (int) WalReceiverPID)));
		fc_signal_child(WalReceiverPID, (SendStop ? SIGSTOP : SIGQUIT));
	}

	/* 也处理 autovacuum 启动器 */
	if (fc_pid == AutoVacPID)
		AutoVacPID = 0;
	else if (AutoVacPID != 0 && fc_take_action)
	{
		ereport(DEBUG2,
				(errmsg_internal("sending %s to process %d",
								 (SendStop ? "SIGSTOP" : "SIGQUIT"),
								 (int) AutoVacPID)));
		fc_signal_child(AutoVacPID, (SendStop ? SIGSTOP : SIGQUIT));
	}

	/* 也处理 archiver */
	if (fc_pid == PgArchPID)
		PgArchPID = 0;
	else if (PgArchPID != 0 && fc_take_action)
	{
		ereport(DEBUG2,
				(errmsg_internal("sending %s to process %d",
								 (SendStop ? "SIGSTOP" : "SIGQUIT"),
								 (int) PgArchPID)));
		fc_signal_child(PgArchPID, (SendStop ? SIGSTOP : SIGQUIT));
	}

	/* 我们不重启 syslogger */

	if (Shutdown != ImmediateShutdown)
		FatalError = true;

	/* 我们现在过渡到等待子进程死亡的状态 */
	if (pmState == PM_RECOVERY ||
		pmState == PM_HOT_STANDBY ||
		pmState == PM_RUN ||
		pmState == PM_STOP_BACKENDS ||
		pmState == PM_SHUTDOWN)
		pmState = PM_WAIT_BACKENDS;

	/*
	 * .. 如果这没有尽快发生，那么现在计时开始，
	 * 我们将毫不留情地杀死它们。
	 */
	if (AbortStartTime == 0)
		AbortStartTime = time(NULL);
}

/*
 * 记录子进程的死亡。
 */
static void fc_LogChildExit(int fc_lev, const char *fc_procname, int fc_pid, int fc_exitstatus)
{
	/*
	 * activity_buffer 的大小是任意的，但设置为默认值
	 * track_activity_query_size
	 */
	char		fc_activity_buffer[1024];
	const char *fc_activity = NULL;

	if (!EXIT_STATUS_0(fc_exitstatus))
		fc_activity = pgstat_get_crashed_backend_activity(fc_pid,
													   fc_activity_buffer,
													   sizeof(fc_activity_buffer));

	if (WIFEXITED(fc_exitstatus))
		ereport(fc_lev,

		/*------
		  翻译者：%s 是描述子进程的名词短语，例如
		  "服务器进程" */
				(errmsg("%s (PID %d) exited with exit code %d",
						fc_procname, fc_pid, WEXITSTATUS(fc_exitstatus)),
				 fc_activity ? errdetail("Failed process was running: %s", fc_activity) : 0));
	else if (WIFSIGNALED(fc_exitstatus))
	{
#if defined(WIN32)
		ereport(lev,

		/*------
		  翻译者：%s 是描述子进程的名词短语，例如
		  "服务器进程" */
				(errmsg("%s (PID %d) was terminated by exception 0x%X",
						procname, pid, WTERMSIG(exitstatus)),
				 errhint("See C include file \"ntstatus.h\" for a description of the hexadecimal value."),
				 activity ? errdetail("Failed process was running: %s", activity) : 0));
#else
		ereport(fc_lev,

		/*------
		  翻译者：%s 是描述子进程的名词短语，例如
		  "服务器进程" */
				(errmsg("%s (PID %d) was terminated by signal %d: %s",
						fc_procname, fc_pid, WTERMSIG(fc_exitstatus),
						pg_strsignal(WTERMSIG(fc_exitstatus))),
				 fc_activity ? errdetail("Failed process was running: %s", fc_activity) : 0));
#endif
	}
	else
		ereport(fc_lev,

		/*------
		  翻译者：%s 是描述子进程的名词短语，例如
		  "服务器进程" */
				(errmsg("%s (PID %d) exited with unrecognized status %d",
						fc_procname, fc_pid, fc_exitstatus),
				 fc_activity ? errdetail("Failed process was running: %s", fc_activity) : 0));
}

/*
 * 推进主进程的状态机，并根据需要采取适当的行动
 *
 * 这是 pmdie()、reaper() 和 sigusr1_handler() 的通用代码，
 * 它们接收可能意味着需要更改状态的信号。
 */
static void fc_PostmasterStateMachine(void)
{
	/* 如果我们正在进行智能关闭，尝试推进该状态。 */
	if (pmState == PM_RUN || pmState == PM_HOT_STANDBY)
	{
		if (!connsAllowed)
		{
			/*
			 * 当没有正常客户端后台进程在运行时，这个状态结束。
			 * 然后我们准备停止其他子进程。
			 */
			if (fc_CountChildren(BACKEND_TYPE_NORMAL) == 0)
				pmState = PM_STOP_BACKENDS;
		}
	}

	/*
	 * 如果我们准备这样做，则向子进程发送关闭信号。 （这
	 * 不是一个持久状态，但将其视为一个独特的 pmState 使
	 * 我们能在多个关闭代码路径中共享这一代码。）
	 */
	if (pmState == PM_STOP_BACKENDS)
	{
		/*
		 * 忘记任何待处理的后台工作进程请求，因为我们不再
		 * 愿意启动任何新的工作进程。（如果接收到额外请求，BackgroundWorkerStateChange 将拒绝它们。）
		 */
		ForgetUnstartedBackgroundWorkers();

		/* 向所有后台子进程发送信号，除了 walsenders */
		fc_SignalSomeChildren(SIGTERM,
						   BACKEND_TYPE_ALL - BACKEND_TYPE_WALSND);
		/* 以及 autovac 启动器 */
		if (AutoVacPID != 0)
			fc_signal_child(AutoVacPID, SIGTERM);
		/* 和 bgwriter 也一样 */
		if (BgWriterPID != 0)
			fc_signal_child(BgWriterPID, SIGTERM);
		/* 和 walwriter 也一样 */
		if (WalWriterPID != 0)
			fc_signal_child(WalWriterPID, SIGTERM);
		/* 如果我们处于恢复状态，也停止启动和 walreceiver 进程 */
		if (StartupPID != 0)
			fc_signal_child(StartupPID, SIGTERM);
		if (WalReceiverPID != 0)
			fc_signal_child(WalReceiverPID, SIGTERM);
		/* 检查器、归档程序、统计和系统记录器现在可以继续 */

		/* 现在转换为 PM_WAIT_BACKENDS 状态，以等待它们结束 */
		pmState = PM_WAIT_BACKENDS;
	}

	/*
	 * 如果我们处于一个隐含等待后端退出的状态机状态，看看它们是否都已消失，如果是，则更改状态。
	 */
	if (pmState == PM_WAIT_BACKENDS)
	{
		/*
		 * PM_WAIT_BACKENDS 状态在我们没有常规后端（包括 autovac 工作线程）、没有 bgworkers（包括未连接的）
		 * 以及没有 walwriter、autovac 启动器或 bgwriter 时结束。如果我们正在进行崩溃恢复或立即关闭，那么我们期望
		 * 检查器也会退出，否则则不会。统计和系统记录器进程被忽略，因为它们没有连接到
		 * 共享内存；我们也在这里忽略了 dead_end 子进程。Walsenders
		 * 和归档程序也被忽略，它们将在写入检查点记录后稍后终止。
		 */
		if (fc_CountChildren(BACKEND_TYPE_ALL - BACKEND_TYPE_WALSND) == 0 &&
			StartupPID == 0 &&
			WalReceiverPID == 0 &&
			BgWriterPID == 0 &&
			(CheckpointerPID == 0 ||
			 (!FatalError && Shutdown < ImmediateShutdown)) &&
			WalWriterPID == 0 &&
			AutoVacPID == 0)
		{
			if (Shutdown >= ImmediateShutdown || FatalError)
			{
				/*
				 * 开始等待 dead_end 子进程退出。这个状态
				 * 更改导致 ServerLoop 停止创建新的子进程。
				 */
				pmState = PM_WAIT_DEAD_END;

				/*
				 * 当我们开始立即关闭或进入
				 * FatalError 状态时，我们已经发送了 SIGQUIT 信号给归档程序和统计进程（如果有）。
				 */
			}
			else
			{
				/*
				 * 如果我们来到这里，我们正在进行正常关闭。所有
				 * 常规子进程都已经消失，现在是告诉
				 * 检查器执行关闭检查点的时候了。
				 */
				Assert(Shutdown > NoShutdown);
				/* 如果检查器没有运行，则启动检查器 */
				if (CheckpointerPID == 0)
					CheckpointerPID = StartCheckpointer();
				/* 并告诉它关闭 */
				if (CheckpointerPID != 0)
				{
					fc_signal_child(CheckpointerPID, SIGUSR2);
					pmState = PM_SHUTDOWN;
				}
				else
				{
					/*
					 * 如果我们未能创建一个检查器，直接关闭。
					 * 任何必需的清理将在下一次重启时进行。我们
					 * 设置 FatalError，以便在退出时记录“异常关闭”消息。
					 */
					FatalError = true;
					pmState = PM_WAIT_DEAD_END;

					/* 也终止 walsenders 和归档程序 */
					SignalChildren(SIGQUIT);
					if (PgArchPID != 0)
						fc_signal_child(PgArchPID, SIGQUIT);
				}
			}
		}
	}

	if (pmState == PM_SHUTDOWN_2)
	{
		/*
		 * PM_SHUTDOWN_2 状态在除了
		 * dead_end 子进程外没有其他子进程时结束。到现在为止，不应该还有任何常规后端；
		 * 我们真正等待的是 walsenders 和
		 * 归档程序。
		 */
		if (PgArchPID == 0 && fc_CountChildren(BACKEND_TYPE_ALL) == 0)
		{
			pmState = PM_WAIT_DEAD_END;
		}
	}

	if (pmState == PM_WAIT_DEAD_END)
	{
		/*
		 * PM_WAIT_DEAD_END 状态在 BackendList 完全为空时结束
		 * （即，没有 dead_end 子进程残留），归档程序也消失。
		 *
		 * 我们等待这两者的原因是为了保护它们免受新
		 * postmaster 启动冲突的子进程；这并不是一个
		 * 铁定的保护，但至少在
		 * 关闭并立即重启的情况下会有所帮助。请注意，它们已经
		 * 收到了适当的关闭信号，要么是在
		 * 导致 PM_WAIT_DEAD_END 的正常状态转换中，要么是在
		 * FatalError 处理中。
		 */
		if (dlist_is_empty(&BackendList) && PgArchPID == 0)
		{
			/* 这些其他进程现在应该已经死亡 */
			Assert(StartupPID == 0);
			Assert(WalReceiverPID == 0);
			Assert(BgWriterPID == 0);
			Assert(CheckpointerPID == 0);
			Assert(WalWriterPID == 0);
			Assert(AutoVacPID == 0);
			/* syslogger 在这里不算 */
			pmState = PM_NO_CHILDREN;
		}
	}

	/*
	 * 如果我们被告知要关闭，我们将在没有
	 * 剩余子进程时立即退出。如果发生崩溃，清理将在
	 * 下一次启动时进行。（在 PostgreSQL 8.3 之前，我们试图在退出之前从崩溃中恢复，
	 * 但如果我们是因为从 init 收到 SIGTERM 而退出，这似乎是不明智的——在 init 决定 SIGKILL 我们之前，可能没有时间进行恢复。）
	 *
	 * 请注意，syslogger 继续运行。它将在其输入管道上看到
	 * EOF 时退出，这发生在没有更多上游
	 * 进程时。
	 */
	if (Shutdown > NoShutdown && pmState == PM_NO_CHILDREN)
	{
		if (FatalError)
		{
			ereport(LOG, (errmsg("abnormal database system shutdown")));
			fc_ExitPostmaster(1);
		}
		else
		{
			/*
			 * postmaster 的正常退出在这里。我们不需要记录
			 * 任何内容，因为 UnlinkLockFiles proc_exit 回调
			 * 将会这样做，而这应该是最后一个用户可见的操作。
			 */
			fc_ExitPostmaster(0);
		}
	}

	/*
	 * 如果启动进程失败，或用户不希望在后端崩溃后自动
	 * 重启，等待所有非 syslogger 子进程退出，然后退出 postmaster。我们不会在
	 * 启动进程失败时尝试重新初始化，因为它更可能会再次失败，
	 * 我们将永远不断尝试。
	 */
	if (pmState == PM_NO_CHILDREN)
	{
		if (StartupStatus == STARTUP_CRASHED)
		{
			ereport(LOG,
					(errmsg("shutting down due to startup process failure")));
			fc_ExitPostmaster(1);
		}
		if (!restart_after_crash)
		{
			ereport(LOG,
					(errmsg("shutting down because restart_after_crash is off")));
			fc_ExitPostmaster(1);
		}
	}

	/*
	 * 如果我们需要从崩溃中恢复，等待所有非 syslogger 子进程
	 * 退出，然后重置 shmem 和 StartupDataBase。
	 */
	if (FatalError && pmState == PM_NO_CHILDREN)
	{
		ereport(LOG,
				(errmsg("all server processes terminated; reinitializing")));

		/* 在崩溃后删除遗留的临时文件 */
		if (remove_temp_files_after_crash)
			RemovePgTempFiles();

		/* 允许后台工作线程立即重启 */
		ResetBackgroundWorkerCrashTimes();

		shmem_exit(1);

		/* 重新读取控制文件到本地内存 */
		LocalProcessControlFile(true);

		fc_reset_shared();

		StartupPID = StartupDataBase();
		Assert(StartupPID != 0);
		StartupStatus = STARTUP_RUNNING;
		pmState = PM_STARTUP;
		/* 崩溃恢复已开始，重置 SIGKILL 标志 */
		AbortStartTime = 0;
	}
}



/*
 * 发送信号给一个postmaster子进程
 *
 * 在具有setsid()的系统上，每个子进程将自己设置为一个
 * 进程组领导者。对于通常以适当方式解释的信号，我们会发信号给整个进程组，而不只是直接的子进程。
 * 这允许我们，例如，SIGQUIT一个被阻塞的archive_recovery脚本，或SIGINT一个通过
 * system()由后端运行的脚本。
 *
 * 对于最近被fork的子进程存在一个竞态条件：它们可能还没有执行setsid()。所以我们直接向子进程
 * 发信号，也向进程组发信号。我们假设这样的子进程会在尝试生成任何孙子进程之前处理信号。
 * 我们还假设对子进程发两次信号不会造成任何问题。
 */
static void fc_signal_child(pid_t fc_pid, int fc_signal)
{
	if (kill(fc_pid, fc_signal) < 0)
		elog(DEBUG3, "kill(%ld,%d) failed: %m", (long) fc_pid, fc_signal);
#ifdef HAVE_SETSID
	switch (fc_signal)
	{
		case SIGINT:
		case SIGTERM:
		case SIGQUIT:
		case SIGSTOP:
		case SIGKILL:
			if (kill(-fc_pid, fc_signal) < 0)
				elog(DEBUG3, "kill(%ld,%d) failed: %m", (long) (-fc_pid), fc_signal);
			break;
		default:
			break;
	}
#endif
}

/*
 * 发送信号给目标子进程（但不是特殊子进程；
 * dead_end子进程永远不会被发信号）。
 */
static bool fc_SignalSomeChildren(int fc_signal, int fc_target)
{
	dlist_iter	fc_iter;
	bool		fc_signaled = false;

	dlist_foreach(fc_iter, &BackendList)
	{
		Backend    *fc_bp = dlist_container(Backend, elem, fc_iter.cur);

		if (fc_bp->dead_end)
			continue;

		/*
		 * 由于target == BACKEND_TYPE_ALL是最常见的情况，我们首先测试
		 * 它，并避免为每个子进程访问共享内存。
		 */
		if (fc_target != BACKEND_TYPE_ALL)
		{
			/*
			 * 为任何最近宣布的WAL Sender进程分配bkend_type。
			 */
			if (fc_bp->bkend_type == BACKEND_TYPE_NORMAL &&
				IsPostmasterChildWalSender(fc_bp->child_slot))
				fc_bp->bkend_type = BACKEND_TYPE_WALSND;

			if (!(fc_target & fc_bp->bkend_type))
				continue;
		}

		ereport(DEBUG4,
				(errmsg_internal("sending signal %d to process %d",
								 fc_signal, (int) fc_bp->pid)));
		fc_signal_child(fc_bp->pid, fc_signal);
		fc_signaled = true;
	}
	return fc_signaled;
}

/*
 * 发送终止信号给子进程。 这考虑了我们所有的子进程，
 * 除了 syslogger 和 dead_end 后台进程。
 */
static void fc_TerminateChildren(int fc_signal)
{
	SignalChildren(fc_signal);
	if (StartupPID != 0)
	{
		fc_signal_child(StartupPID, fc_signal);
		if (fc_signal == SIGQUIT || fc_signal == SIGKILL)
			StartupStatus = STARTUP_SIGNALED;
	}
	if (BgWriterPID != 0)
		fc_signal_child(BgWriterPID, fc_signal);
	if (CheckpointerPID != 0)
		fc_signal_child(CheckpointerPID, fc_signal);
	if (WalWriterPID != 0)
		fc_signal_child(WalWriterPID, fc_signal);
	if (WalReceiverPID != 0)
		fc_signal_child(WalReceiverPID, fc_signal);
	if (AutoVacPID != 0)
		fc_signal_child(AutoVacPID, fc_signal);
	if (PgArchPID != 0)
		fc_signal_child(PgArchPID, fc_signal);
}

/*
 * BackendStartup -- 启动后台进程
 *
 * 返回：如果 fork 失败则返回 STATUS_ERROR，其他情况返回 STATUS_OK。
 *
 * 注意：如果你更改了这段代码，请考虑 StartAutovacuumWorker。
 */
static int fc_BackendStartup(Port *fc_port)
{
	Backend    *fc_bn;				/* 用于后台清理 */
	pid_t		fc_pid;

	/*
	 * 创建后台数据结构。 最好在 fork() 之前这样做，以便我们可以
	 * 妥善处理失败。
	 */
	fc_bn = (Backend *) malloc(sizeof(Backend));
	if (!fc_bn)
	{
		ereport(LOG,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("out of memory")));
		return STATUS_ERROR;
	}

	/*
	 * 计算将分配给该后台的取消键。后台将在 fork 后的进程中有其
	 * 自己的 MyCancelKey 副本，以便能够将键传输到前端。
	 */
	if (!fc_RandomCancelKey(&MyCancelKey))
	{
		free(fc_bn);
		ereport(LOG,
				(errcode(ERRCODE_INTERNAL_ERROR),
				 errmsg("could not generate random cancel key")));
		return STATUS_ERROR;
	}

	fc_bn->cancel_key = MyCancelKey;

	/* 传递 canAcceptConnections 状态 */
	fc_port->canAcceptConnections = fc_canAcceptConnections(BACKEND_TYPE_NORMAL);
	fc_bn->dead_end = (fc_port->canAcceptConnections != CAC_OK);

	/*
	 * 除非是 dead_end 子进程，否则分配给它一个子插槽编号
	 */
	if (!fc_bn->dead_end)
		fc_bn->child_slot = MyPMChildSlot = AssignPostmasterChildSlot();
	else
		fc_bn->child_slot = 0;

	/* 还没有要求通知任何 bgworkers */
	fc_bn->bgworker_notify = false;

#ifdef EXEC_BACKEND
	fc_pid = fc_backend_forkexec(fc_port);
#else							/* !EXEC_BACKEND */
	fc_pid = fork_process();
	if (fc_pid == 0)				/* child */
	{
		free(fc_bn);

		/* 解绑 postmaster */
		InitPostmasterChild();

		/* 关闭 postmaster 的套接字 */
		ClosePostmasterPorts(false);

		/* 执行额外初始化并收集启动数据包 */
		fc_BackendInitialize(fc_port);

		/*
		 * 在共享内存中为每个后台创建一个 PGPROC 结构。我们必须在
		 * 使用 LWLocks 之前做到这一点。在 !EXEC_BACKEND 情况下（此处）
		 * 这可以稍微延迟，但 EXEC_BACKEND 需要在 PostgresMain()
		 * 之前处理 LWLocks，因此我们在此处也进行对称处理。
		 */
		InitProcess();

		/* 然后运行后台 */
		fc_BackendRun(fc_port);
	}
#endif							/*
 * PGSharedMemoryDetach
 *
 * 从共享内存段分离，如果仍然附加。这不是旨在由最初创建该段的进程显式调用（它将注册 on_shmem_exit 回调来做到这一点）。相反，这是为那些继承了附加并希望摆脱它的子进程而设。
 *
 * UsedShmemSegID 和 UsedShmemSegAddr 也是该例程的隐式参数，还有 AnonymousShmem 和 AnonymousShmemSize。
 */

	if (fc_pid < 0)
	{
		/* 在父进程中，fork 失败 */
		int			fc_save_errno = errno;

		if (!fc_bn->dead_end)
			(void) ReleasePostmasterChildSlot(fc_bn->child_slot);
		free(fc_bn);
		errno = fc_save_errno;
		ereport(LOG,
				(errmsg("could not fork new process for connection: %m")));
		fc_report_fork_failure_to_client(fc_port, fc_save_errno);
		return STATUS_ERROR;
	}

	/* 在父进程中，fork 成功 */
	ereport(DEBUG2,
			(errmsg_internal("forked new backend, pid=%d socket=%d",
							 (int) fc_pid, (int) fc_port->sock)));

	/*
	 * 一切都很成功，现在可以将这个后台添加到我们的后台列表中。
	 */
	fc_bn->pid = fc_pid;
	fc_bn->bkend_type = BACKEND_TYPE_NORMAL;	/* 可以稍后更改为 WALSND */
	dlist_push_head(&BackendList, &fc_bn->elem);

#ifdef EXEC_BACKEND
	if (!fc_bn->dead_end)
		fc_ShmemBackendArrayAdd(fc_bn);
#endif

	return STATUS_OK;
}

/*
 * 尝试在关闭连接之前向客户端报告后台 fork() 失败。由于我们不希望冒
 * 着阻塞 postmaster 的风险，我们将连接设置为非阻塞并且只尝试一次。
 *
 * 这是非常特殊用途的代码；我们不能使用后台 libpq，因为它尚未启动
 * 和运行。
 */
static void fc_report_fork_failure_to_client(Port *fc_port, int fc_errnum)
{
	char		fc_buffer[1000];
	int			fc_rc;

	/* 格式化错误消息数据包（始终使用 V2 协议） */
	snprintf(fc_buffer, sizeof(fc_buffer), "E%s%s\n",
			 _("could not fork new process for connection: "),
			 strerror(fc_errnum));

	/* 将端口设置为非阻塞。 如果这失败则不要执行 send() */
	if (!pg_set_noblock(fc_port->sock))
		return;

	/* 我们将在 EINTR 后重试，但忽略其他所有失败 */
	do
	{
		fc_rc = send(fc_port->sock, fc_buffer, strlen(fc_buffer) + 1, 0);
	} while (fc_rc < 0 && errno == EINTR);
}


/*
 * BackendInitialize -- 初始化一个交互式（postmaster 子进程）
 *				后台进程，并收集客户端的启动数据包。
 *
 * 返回：无。 如果有任何失败将不会返回。
 *
 * 注意：这段代码不依赖于访问共享内存。
 * 实际上，我们对 SIGTERM/超时处理的处理 *要求* 共享内存尚未
 * 被触摸；请参见其中的注释。
 * 在 EXEC_BACKEND 情况下，我们物理上连接到共享内存，但尚未设置
 * 大部分本地指针指向 shmem 结构。
 */
static void fc_BackendInitialize(Port *fc_port)
{
	int			fc_status;
	int			fc_ret;
	char		fc_remote_host[NI_MAXHOST];
	char		fc_remote_port[NI_MAXSERV];
	StringInfoData fc_ps_data;

	/* 保存端口等以便用于 ps 状态 */
	MyProcPort = fc_port;

	/* 告诉 fd.c 关于与端口关联的长生命周期 FD */
	ReserveExternalFD();

	/*
	 * PreAuthDelay 是一个调试辅助工具，用于调查身份验证循环中的问题：
	 * 它可以在 postgresql.conf 中设置，以允许时间附加到新分叉的后台
	 * 进程，使用调试器。 （另见 PostAuthDelay，我们允许客户端通过 PGOPTIONS 
	 * 传递，但在身份验证完成之前不会被尊重。）
	 */
	if (PreAuthDelay > 0)
		pg_usleep(PreAuthDelay * 1000000L);

	/* 此标志将在 InitPostgres 完成身份验证之前保持设置 */
	ClientAuthInProgress = true;	/* 限制日志消息的可见性 */

	/* 将这些设置为空，以防在我们设置它们之前需要它们 */
	fc_port->remote_host = "";
	fc_port->remote_port = "";

	/*
	 * 初始化libpq并启用将ereport错误报告到客户端的功能。
	 * 必须现在执行此操作，因为身份验证使用libpq发送消息。
	 */
	pq_init();					/* 初始化libpq以与客户端通信 */
	whereToSendOutput = DestRemote; /* 现在可以安全地向客户端ereport */

	/*
	 * 如果我们在尝试收集启动数据包时接收到SIGTERM或超时，我们将安排执行_exit(1)；而
	 * SIGQUIT导致_exit(2)。
	 * 否则，如果有错误的客户端未能及时发送数据包，主控程序无法快速或立即
	 * 优雅地关闭数据库。
	 *
	 * 通过_exit(1)退出是唯一可能的，因为我们尚未触及
	 * 共享内存；因此不需要清理任何进程外状态。
	 */
	pqsignal(SIGTERM, fc_process_startup_packet_die);
	/* SIGQUIT 处理程序已经由 InitPostmasterChild 设置 */
	InitializeTimeouts();		/* 建立 SIGALRM 处理程序 */
	PG_SETMASK(&StartupBlockSig);

	/*
	 * 获取远程主机名和端口以进行日志记录和状态显示。
	 */
	fc_remote_host[0] = '\0';
	fc_remote_port[0] = '\0';
	if ((fc_ret = pg_getnameinfo_all(&fc_port->raddr.addr, fc_port->raddr.salen,
								  fc_remote_host, sizeof(fc_remote_host),
								  fc_remote_port, sizeof(fc_remote_port),
								  (log_hostname ? 0 : NI_NUMERICHOST) | NI_NUMERICSERV)) != 0)
		ereport(WARNING,
				(errmsg_internal("pg_getnameinfo_all() failed: %s",
								 gai_strerror(fc_ret))));

	/*
	 * 将remote_host和remote_port保存在端口结构中（在此之后，它们
	 * 将出现在日志消息的log_line_prefix数据中）。
	 */
	fc_port->remote_host = strdup(fc_remote_host);
	fc_port->remote_port = strdup(fc_remote_port);

	/* 现在如果需要可以发出Log_connections消息 */
	if (Log_connections)
	{
		if (fc_remote_port[0])
			ereport(LOG,
					(errmsg("connection received: host=%s port=%s",
							fc_remote_host,
							fc_remote_port)));
		else
			ereport(LOG,
					(errmsg("connection received: host=%s",
							fc_remote_host)));
	}

	/*
	 * 如果我们进行了反向查找以获取名称，我们不妨保存结果
	 * 而不是可能在身份验证期间重复查找。
	 *
	 * 请注意，我们不想在上面指定NI_NAMEREQD，因为那样我们将
	 * 得不到对没有rDNS条目的客户端有用的信息。因此，我们
	 * 必须检查我们是否得到了数字IPv4或IPv6地址，如果是，则不
	 * 将其保存到remote_hostname中。（此测试是保守的，可能
	 * 有时将主机名归类为数字，但在那个方向上的错误是安全的；
	 * 这只是导致可能的额外查找。）
	 */
	if (log_hostname &&
		fc_ret == 0 &&
		strspn(fc_remote_host, "0123456789.") < strlen(fc_remote_host) &&
		strspn(fc_remote_host, "0123456789ABCDEFabcdef:") < strlen(fc_remote_host))
		fc_port->remote_hostname = strdup(fc_remote_host);

	/*
	 * 准备开始客户端交互。我们将在延迟后放弃并_exit(1)，以便
	 * 破损客户端无法无限期占用连接。
	 * PreAuthDelay和以上的任何DNS交互不算在时间限制内。
	 *
	 * 注意：AuthenticationTimeout在等待启动数据包时应用，
	 * 然后在InitPostgres中应用于任何身份验证操作的持续时间。
	 * 因此，一个恶意客户端可能会将进程占用近两倍于AuthenticationTimeout的
	 * 时间，然后我们将他踢走。
	 *
	 * 注意：由于PostgresMain将再次调用InitializeTimeouts，
	 * STARTUP_PACKET_TIMEOUT的注册将会丢失。这是可以的，
	 * 因为在此函数之后我们不再使用它。
	 */
	RegisterTimeout(STARTUP_PACKET_TIMEOUT, fc_StartupPacketTimeoutHandler);
	enable_timeout_after(STARTUP_PACKET_TIMEOUT, AuthenticationTimeout * 1000);

	/*
	 * 接收启动数据包（这可能变成取消请求
	 * 数据包）。
	 */
	fc_status = fc_ProcessStartupPacket(fc_port, false, false);

	/*
	 * 禁用超时，并再次防止SIGTERM。
	 */
	disable_timeout(STARTUP_PACKET_TIMEOUT, false);
	PG_SETMASK(&BlockSig);

	/*
	 * 作为一种安全检查，以确保在启动过程中还没有进行
	 * 共享内存修改，如果我们通过SIGTERM或超时退出，上述修改将需要撤销，
	 * 检查尚未注册任何on_shmem_exit处理程序。（这并不是绝对安全，
	 * 因为有人可能误用on_proc_exit处理程序进行共享内存清理，
	 * 但这是一个便宜且有帮助的检查。不幸的是，我们无法禁止on_proc_exit
	 * 处理程序，因为pq_init()已经注册了一个。）
	 */
	check_on_shmem_exit_lists_are_empty();

	/*
	 * 如果数据包有问题或是取消数据包，则停在这里。ProcessStartupPacket
	 * 已经进行了适当的错误报告。
	 */
	if (fc_status != STATUS_OK)
		proc_exit(0);

	/*
	 * 现在我们有了用户和数据库名称，可以设置进程
	 * 标题以便于ps。最好在启动时尽早执行此操作。
	 */
	initStringInfo(&fc_ps_data);
	if (am_walsender)
		appendStringInfo(&fc_ps_data, "%s ", GetBackendTypeDesc(B_WAL_SENDER));
	appendStringInfo(&fc_ps_data, "%s ", fc_port->user_name);
	if (!am_walsender)
		appendStringInfo(&fc_ps_data, "%s ", fc_port->database_name);
	appendStringInfo(&fc_ps_data, "%s", fc_port->remote_host);
	if (fc_port->remote_port[0] != '\0')
		appendStringInfo(&fc_ps_data, "(%s)", fc_port->remote_port);

	init_ps_display(fc_ps_data.data);
	pfree(fc_ps_data.data);

	set_ps_display("initializing");
}



/*********************************************************
 * BackendRun -- 设置后端的参数列表并调用 PostgresMain()
 *
 * 返回：
 *		不会返回。
 */
static void fc_BackendRun(Port *fc_port)
{
	/*
	 * 确保我们不再处于 PostmasterContext 中。 （不过我们还不能删除它，因为 InitPostgres 需要 HBA 数据。）
	 */
	MemoryContextSwitchTo(TopMemoryContext);

	PostgresMain(fc_port->database_name, fc_port->user_name);
}


#ifdef EXEC_BACKEND

/*
 * postmaster_forkexec -- 创建并执行一个 postmaster 子进程
 *
 * 调用者必须已经设置好 argv 数组，除了 argv[2]
 * 将被填充为临时变量文件的名称。
 *
 * 返回子进程 PID，或在 fork 失败时返回 -1 （失败时已记录适当的错误信息）。
 *
 * 此例程的所有使用将在子进程中调用 SubPostmasterMain。
 */
pid_t postmaster_forkexec(int fc_argc, char *fc_argv[])
{
	Port		fc_port;

	/* 这个入口点为 Port 变量传递虚拟值 */
	memset(&fc_port, 0, sizeof(fc_port));
	return fc_internal_forkexec(fc_argc, fc_argv, &fc_port);
}

/*
 * backend_forkexec -- 创建/执行后端进程
 *
 * 一些操作系统（WIN32）没有 fork()，所以我们必须通过存储
 * 需要传递给子进程的参数来模拟它，然后创建一个新的子进程。
 *
 * 返回 fork/exec 的进程的 pid，或在失败时返回 -1
 */
static pid_t fc_backend_forkexec(Port *fc_port)
{
	char	   *fc_av[4];
	int			fc_ac = 0;

	fc_av[fc_ac++] = "postgres";
	fc_av[fc_ac++] = "--forkbackend";
	fc_av[fc_ac++] = NULL;			/* 由 internal_forkexec 填充 */

	fc_av[fc_ac] = NULL;
	Assert(fc_ac < lengthof(fc_av));

	return fc_internal_forkexec(fc_ac, fc_av, fc_port);
}

#ifndef WIN32

/*
 * internal_forkexec 非 win32 实现
 *
 * - 将后端变量写入参数文件
 * - fork()，然后 exec() 子进程
 */
static pid_t fc_internal_forkexec(int fc_argc, char *fc_argv[], Port *fc_port)
{
	static unsigned long fc_tmpBackendFileNum = 0;
	pid_t		fc_pid;
	char		fc_tmpfilename[MAXPGPATH];
	BackendParameters fc_param;
	FILE	   *fc_fp;

	if (!fc_save_backend_variables(&fc_param, fc_port))
		return -1;				/* 由 save_backend_variables 创建的日志 */

	/* 计算临时文件的名称 */
	snprintf(fc_tmpfilename, MAXPGPATH, "%s/%s.backend_var.%d.%lu",
			 PG_TEMP_FILES_DIR, PG_TEMP_FILE_PREFIX,
			 MyProcPid, ++fc_tmpBackendFileNum);

	/* 打开文件 */
	fc_fp = AllocateFile(fc_tmpfilename, PG_BINARY_W);
	if (!fc_fp)
	{
		/*
		 * 正如在 OpenTemporaryFileInTablespace 中，尝试创建临时文件
		 * 目录，忽略错误。
		 */
		(void) MakePGDirectory(PG_TEMP_FILES_DIR);

		fc_fp = AllocateFile(fc_tmpfilename, PG_BINARY_W);
		if (!fc_fp)
		{
			ereport(LOG,
					(errcode_for_file_access(),
					 errmsg("could not create file \"%s\": %m",
							fc_tmpfilename)));
			return -1;
		}
	}

	if (fwrite(&fc_param, sizeof(fc_param), 1, fc_fp) != 1)
	{
		ereport(LOG,
				(errcode_for_file_access(),
				 errmsg("could not write to file \"%s\": %m", fc_tmpfilename)));
		FreeFile(fc_fp);
		return -1;
	}

	/* 释放文件 */
	if (FreeFile(fc_fp))
	{
		ereport(LOG,
				(errcode_for_file_access(),
				 errmsg("could not write to file \"%s\": %m", fc_tmpfilename)));
		return -1;
	}

	/* 确保调用者正确设置了 argv */
	Assert(fc_argc >= 3);
	Assert(fc_argv[fc_argc] == NULL);
	Assert(strncmp(fc_argv[1], "--fork", 6) == 0);
	Assert(fc_argv[2] == NULL);

	/* 在 --fork 参数后插入临时文件名称 */
	fc_argv[2] = fc_tmpfilename;

	/* 在子进程中执行 execv */
	if ((fc_pid = fork_process()) == 0)
	{
		if (execv(postgres_exec_path, fc_argv) < 0)
		{
			ereport(LOG,
					(errmsg("could not execute server process \"%s\": %m",
							postgres_exec_path)));
			/* 我们已经在子进程中，这里不能返回 */
			exit(1);
		}
	}

	return fc_pid;					/* 父进程返回 pid，或在 fork 失败时返回 -1 */
}
#else							/* WIN32 */

/*
 * internal_forkexec win32 实现
 *
 * - 使用 CreateProcess() 启动后台进程，处于挂起状态
 * - 将后台变量写入参数文件
 *	- 在此过程中，复制所需的句柄和套接字，以便在新进程中继承
 * - 一旦后台参数文件完成，恢复新进程的执行。
 */
static pid_t internal_forkexec(int argc, char *argv[], Port *port)
{
	int			retry_count = 0;
	STARTUPINFO si;
	PROCESS_INFORMATION pi;
	int			i;
	int			j;
	char		cmdLine[MAXPGPATH * 2];
	HANDLE		paramHandle;
	BackendParameters *param;
	SECURITY_ATTRIBUTES sa;
	char		paramHandleStr[32];
	win32_deadchild_waitinfo *childinfo;

	/* 确保调用者正确设置了 argv */
	Assert(argc >= 3);
	Assert(argv[argc] == NULL);
	Assert(strncmp(argv[1], "--fork", 6) == 0);
	Assert(argv[2] == NULL);

	/* 如果需要重试，则在此处恢复 */
retry:

	/* 设置用于参数传递的共享内存 */
	ZeroMemory(&sa, sizeof(sa));
	sa.nLength = sizeof(sa);
	sa.bInheritHandle = TRUE;
	paramHandle = CreateFileMapping(INVALID_HANDLE_VALUE,
									&sa,
									PAGE_READWRITE,
									0,
									sizeof(BackendParameters),
									NULL);
	if (paramHandle == INVALID_HANDLE_VALUE)
	{
		ereport(LOG,
				(errmsg("could not create backend parameter file mapping: error code %lu",
						GetLastError())));
		return -1;
	}

	param = MapViewOfFile(paramHandle, FILE_MAP_WRITE, 0, 0, sizeof(BackendParameters));
	if (!param)
	{
		ereport(LOG,
				(errmsg("could not map backend parameter memory: error code %lu",
						GetLastError())));
		CloseHandle(paramHandle);
		return -1;
	}

	/* 在 --fork 参数后插入临时文件名称 */
#ifdef _WIN64
	sprintf(paramHandleStr, "%llu", (LONG_PTR) paramHandle);
#else
	sprintf(paramHandleStr, "%lu", (DWORD) paramHandle);
#endif
	argv[2] = paramHandleStr;

	/* 格式化命令行 */
	cmdLine[sizeof(cmdLine) - 1] = '\0';
	cmdLine[sizeof(cmdLine) - 2] = '\0';
	snprintf(cmdLine, sizeof(cmdLine) - 1, "\"%s\"", postgres_exec_path);
	i = 0;
	while (argv[++i] != NULL)
	{
		j = strlen(cmdLine);
		snprintf(cmdLine + j, sizeof(cmdLine) - 1 - j, " \"%s\"", argv[i]);
	}
	if (cmdLine[sizeof(cmdLine) - 2] != '\0')
	{
		ereport(LOG,
				(errmsg("subprocess command line too long")));
		UnmapViewOfFile(param);
		CloseHandle(paramHandle);
		return -1;
	}

	memset(&pi, 0, sizeof(pi));
	memset(&si, 0, sizeof(si));
	si.cb = sizeof(si);

	/*
	 * 以挂起状态创建子进程。稍后将恢复此状态，
	 * 一旦我们写出参数文件。
	 */
	if (!CreateProcess(NULL, cmdLine, NULL, NULL, TRUE, CREATE_SUSPENDED,
					   NULL, NULL, &si, &pi))
	{
		ereport(LOG,
				(errmsg("CreateProcess() call failed: %m (error code %lu)",
						GetLastError())));
		UnmapViewOfFile(param);
		CloseHandle(paramHandle);
		return -1;
	}

	if (!save_backend_variables(param, port, pi.hProcess, pi.dwProcessId))
	{
		/*
		 * 由 save_backend_variables 生成的日志，但我们必须清理
		 * 这个半启动进程的混乱
		 */
		if (!TerminateProcess(pi.hProcess, 255))
			ereport(LOG,
					(errmsg_internal("could not terminate unstarted process: error code %lu",
									 GetLastError())));
		CloseHandle(pi.hProcess);
		CloseHandle(pi.hThread);
		UnmapViewOfFile(param);
		CloseHandle(paramHandle);
		return -1;				/* 由 save_backend_variables 创建的日志 */
	}

	/* 删除现已继承给后台进程的参数共享内存 */
	if (!UnmapViewOfFile(param))
		ereport(LOG,
				(errmsg("could not unmap view of backend parameter file: error code %lu",
						GetLastError())));
	if (!CloseHandle(paramHandle))
		ereport(LOG,
				(errmsg("could not close handle to backend parameter file: error code %lu",
						GetLastError())));

	/*
	 * 在恢复子进程之前，保留我们主共享内存段使用的内存区域。
	 * 通常应该成功，但如果启用了 ASLR，则可能因为
	 * 栈或堆已映射到该范围而偶尔失败。在这种情况下，只需终止
	 * 进程并重试。
	 */
	if (!pgwin32_ReserveSharedMemoryRegion(pi.hProcess))
	{
		/* pgwin32_ReserveSharedMemoryRegion 已经生成了日志条目 */
		if (!TerminateProcess(pi.hProcess, 255))
			ereport(LOG,
					(errmsg_internal("could not terminate process that failed to reserve memory: error code %lu",
									 GetLastError())));
		CloseHandle(pi.hProcess);
		CloseHandle(pi.hThread);
		if (++retry_count < 100)
			goto retry;
		ereport(LOG,
				(errmsg("giving up after too many tries to reserve shared memory"),
				 errhint("This might be caused by ASLR or antivirus software.")));
		return -1;
	}

	/*
	 * 现在后台变量已写出，我们启动子线程，
	 * 以便在设置其余的父状态时可以开始初始化。
	 */
	if (ResumeThread(pi.hThread) == -1)
	{
		if (!TerminateProcess(pi.hProcess, 255))
		{
			ereport(LOG,
					(errmsg_internal("could not terminate unstartable process: error code %lu",
									 GetLastError())));
			CloseHandle(pi.hProcess);
			CloseHandle(pi.hThread);
			return -1;
		}
		CloseHandle(pi.hProcess);
		CloseHandle(pi.hThread);
		ereport(LOG,
				(errmsg_internal("could not resume thread of unstarted process: error code %lu",
								 GetLastError())));
		return -1;
	}

	/*
	 * 排队一个等待者，以在此子进程终止时发出信号。等待将由
	 * 操作系统线程池自动处理。内存将由后续对 waitpid() 的调用释放。
	 */
	childinfo = palloc(sizeof(win32_deadchild_waitinfo));
	childinfo->procHandle = pi.hProcess;
	childinfo->procId = pi.dwProcessId;

	if (!RegisterWaitForSingleObject(&childinfo->waitHandle,
									 pi.hProcess,
									 pgwin32_deadchild_callback,
									 childinfo,
									 INFINITE,
									 WT_EXECUTEONLYONCE | WT_EXECUTEINWAITTHREAD))
		ereport(FATAL,
				(errmsg_internal("could not register process for wait: error code %lu",
								 GetLastError())));

	/* 不要在此处关闭 pi.hProcess - waitpid() 需要访问它 */

	CloseHandle(pi.hThread);

	return pi.dwProcessId;
}
#endif							/* WIN32 */


/*
 * SubPostmasterMain -- 将 fork/exec'd 进程置于状态
 *			等同于如果我们在 Unix 上简单地 fork 了，然后
 *			调度到适当位置。
 *
 * 前两个命令行参数预计为 "--forkFOO"
 * (其中 FOO 表示我们将成为哪个子进程)，以及我们可以读取的变量文件
 * 以加载在 Unix 上由 fork() 继承的数据。剩余参数转到
 * 子进程 FooMain() 例程。
 */
void SubPostmasterMain(int fc_argc, char *fc_argv[])
{
	Port		fc_port;

	/* 在 EXEC_BACKEND 情况下，我们不会继承这些设置 */
	IsPostmasterEnvironment = true;
	whereToSendOutput = DestNone;

	/* 设置基本子系统（确保 elog() 正常工作） */
	InitializeGUCOptions();

	/* 检查我们是否得到了适当的参数 */
	if (fc_argc < 3)
		elog(FATAL, "invalid subpostmaster invocation");

	/* 读取变量文件 */
	memset(&fc_port, 0, sizeof(Port));
	fc_read_backend_variables(fc_argv[2], &fc_port);

	/* 关闭邮政管理器的套接字（尽快知道它们时） */
	ClosePostmasterPorts(strcmp(fc_argv[1], "--forklog") == 0);

	/* 设置为邮政管理器子进程 */
	InitPostmasterChild();

	/*
	 * 如果合适，物理重新连接到共享内存段。我们希望
	 * 在进一步操作之前这样做，以确保我们可以在邮政管理器使用的
	 * 相同地址附加。另一方面，如果我们选择不重新连接，可能
	 * 还有其他清理工作要做。
	 *
	 * 如果在 Linux 上测试 EXEC_BACKEND，您应该在启动
	 * 邮政管理器之前以 root 身份运行以下命令：
	 *
	 * sysctl -w kernel.randomize_va_space=0
	 *
	 * 这可以防止使用随机化的栈和代码地址，导致
	 * 子进程的内存映射与父进程不同，有时使得不可能
	 * 在所需地址附加到共享内存。完成后将设置恢复到
	 * 其旧值（通常为 '1' 或 '2'）。
	 */
	if (strcmp(fc_argv[1], "--forkbackend") == 0 ||
		strcmp(fc_argv[1], "--forkavlauncher") == 0 ||
		strcmp(fc_argv[1], "--forkavworker") == 0 ||
		strcmp(fc_argv[1], "--forkaux") == 0 ||
		strncmp(fc_argv[1], "--forkbgworker=", 15) == 0)
		PGSharedMemoryReAttach();
	else
		PGSharedMemoryNoReAttach();

	/* autovacuum 需要在调用 InitProcess 之前设置此项 */
	if (strcmp(fc_argv[1], "--forkavlauncher") == 0)
		AutovacuumLauncherIAm();
	if (strcmp(fc_argv[1], "--forkavworker") == 0)
		AutovacuumWorkerIAm();

	/* 读取剩余的 GUC 变量 */
	read_nondefault_variables();

	/*
	 * 检查数据目录是否有效，这还将检查数据目录的权限，并更新我们的 umask 和文件/组变量，以便稍后创建文件。注意：这实际上应该在我们创建任何文件或目录之前完成。
	 */
	checkDataDir();

	/*
	 * (重新)读取控制文件，因为它包含配置。邮政主进程已经读取了这个，但这个进程并不知道这一点。
	 */
	LocalProcessControlFile(false);

	/*
	 * 重新加载邮政主进程预加载的任何库。由于我们执行了这个进程，这些库没有随我们一起来；但我们应该将它们加载到所有子进程中，以便与非 EXEC_BACKEND 行为一致。
	 */
	process_shared_preload_libraries();

	/* 运行后端或适当的子进程 */
	if (strcmp(fc_argv[1], "--forkbackend") == 0)
	{
		Assert(fc_argc == 3);		/* 不应该有更多的参数 */

		/*
		 * 需要在后端重新初始化 SSL 库，因为上下文结构包含函数指针，无法通过参数文件传递。
		 *
		 * 如果由于某种原因重新加载失败（可能用户安装了损坏的密钥文件），那么继续进行而不使用 SSL；这比所有连接变得不可能要好。
		 *
		 * XXX 我们应该在所有子进程中这样做吗？目前，在后端子进程中这样做就足够了。
		 */
#ifdef USE_SSL
		if (EnableSSL)
		{
			if (secure_initialize(false) == 0)
				LoadedSSL = true;
			else
				ereport(LOG,
						(errmsg("SSL configuration could not be loaded in child process")));
		}
#endif

		/*
		 * 执行额外的初始化并收集启动数据包。
		 *
		 * 我们希望在 InitProcess() 之前执行此操作，原因有两个：1. 这样我们就不会在等待客户端时占用一个 PGPROC 插槽。2. 如果由于 PGPROC 插槽不足而导致 InitProcess() 失败，我们已经初始化了 libpq，并能够向客户端报告错误。
		 */
		fc_BackendInitialize(&fc_port);

		/* 恢复基本共享内存指针 */
		InitShmemAccess(UsedShmemSegAddr);

		/* 需要一个 PGPROC 来运行 CreateSharedMemoryAndSemaphores */
		InitProcess();

		/* 将进程附加到共享数据结构 */
		CreateSharedMemoryAndSemaphores();

		/* 然后运行后台 */
		fc_BackendRun(&fc_port);		/* 不会返回 */
	}
	if (strcmp(fc_argv[1], "--forkaux") == 0)
	{
		AuxProcType fc_auxtype;

		Assert(fc_argc == 4);

		/* 恢复基本共享内存指针 */
		InitShmemAccess(UsedShmemSegAddr);

		/* 需要一个 PGPROC 来运行 CreateSharedMemoryAndSemaphores */
		InitAuxiliaryProcess();

		/* 将进程附加到共享数据结构 */
		CreateSharedMemoryAndSemaphores();

		fc_auxtype = atoi(fc_argv[3]);
		AuxiliaryProcessMain(fc_auxtype);	/* 不会返回 */
	}
	if (strcmp(fc_argv[1], "--forkavlauncher") == 0)
	{
		/* 恢复基本共享内存指针 */
		InitShmemAccess(UsedShmemSegAddr);

		/* 需要一个 PGPROC 来运行 CreateSharedMemoryAndSemaphores */
		InitProcess();

		/* 将进程附加到共享数据结构 */
		CreateSharedMemoryAndSemaphores();

		AutoVacLauncherMain(fc_argc - 2, fc_argv + 2);	/* 不会返回 */
	}
	if (strcmp(fc_argv[1], "--forkavworker") == 0)
	{
		/* 恢复基本共享内存指针 */
		InitShmemAccess(UsedShmemSegAddr);

		/* 需要一个 PGPROC 来运行 CreateSharedMemoryAndSemaphores */
		InitProcess();

		/* 将进程附加到共享数据结构 */
		CreateSharedMemoryAndSemaphores();

		AutoVacWorkerMain(fc_argc - 2, fc_argv + 2);	/* 不会返回 */
	}
	if (strncmp(fc_argv[1], "--forkbgworker=", 15) == 0)
	{
		int			fc_shmem_slot;

		/* 尽早执行此操作；特别是，在 InitProcess() 之前 */
		IsBackgroundWorker = true;

		/* 恢复基本共享内存指针 */
		InitShmemAccess(UsedShmemSegAddr);

		/* 需要一个 PGPROC 来运行 CreateSharedMemoryAndSemaphores */
		InitProcess();

		/* 将进程附加到共享数据结构 */
		CreateSharedMemoryAndSemaphores();

		/* 从共享内存中获取 MyBgworkerEntry */
		fc_shmem_slot = atoi(fc_argv[1] + 15);
		MyBgworkerEntry = BackgroundWorkerEntry(fc_shmem_slot);

		StartBackgroundWorker();
	}
	if (strcmp(fc_argv[1], "--forklog") == 0)
	{
		/* 不想附加到共享内存 */

		SysLoggerMain(fc_argc, fc_argv);	/* 不会返回 */
	}

	abort();					/* 不应该到这里 */
}
#endif							/*
 * PGSharedMemoryDetach
 *
 * 从共享内存段分离，如果仍然附加。这不是旨在由最初创建该段的进程显式调用（它将注册 on_shmem_exit 回调来做到这一点）。相反，这是为那些继承了附加并希望摆脱它的子进程而设。
 *
 * UsedShmemSegID 和 UsedShmemSegAddr 也是该例程的隐式参数，还有 AnonymousShmem 和 AnonymousShmemSize。
 */


/*
 * ExitPostmaster -- 清理
 *
 * 不要直接调用 exit() --- 始终通过这里！
 */
static void fc_ExitPostmaster(int fc_status)
{
#ifdef HAVE_PTHREAD_IS_THREADED_NP

	/*
	 * 没有已知原因导致邮政主进程在启动后变得多线程。然而，我们可能通过错误退出到达这里，而未能在 PostmasterMain 中进行测试，因此提供与那里相同的提示。
	 * 此消息使用 LOG 级别，因为此时的非正常关机通常看起来与正常关机没有什么不同。
	 */
	if (pthread_is_threaded_np() != 0)
		ereport(LOG,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("postmaster became multithreaded"),
				 errhint("Set the LC_ALL environment variable to a valid locale.")));
#endif

	/* 应该清理共享内存并杀死所有后端 */

	/*
	 * 对于这里的语义不太确定。当邮政主进程死亡时，所有后端是否应该被杀死？可能不应该。
	 *
	 * 必须		-- vadim 1999年5月10日
	 */

	proc_exit(fc_status);
}

/*
 * sigusr1_handler - 处理来自子进程的信号条件
 */
static void fc_sigusr1_handler(SIGNAL_ARGS)
{
	int			fc_save_errno = errno;

	/*
	 * 我们依赖信号机制来阻塞所有信号……除了
	 * Windows，它缺少 sigaction()，所以我们必须手动处理。
	 */
#ifdef WIN32
	PG_SETMASK(&BlockSig);
#endif

	/*
	 * 在意外状态下忽略 RECOVERY_STARTED 和 BEGIN_HOT_STANDBY 信号。
	 * 如果启动进程迅速启动，完成恢复，退出，我们可能会首先处理启动进程的死亡。
	 * 在这种情况下，我们不想回到恢复状态。
	 */
	if (CheckPostmasterSignal(PMSIGNAL_RECOVERY_STARTED) &&
		pmState == PM_STARTUP && Shutdown == NoShutdown)
	{
		/* WAL 重做已开始。我们已不再重新初始化。 */
		FatalError = false;
		AbortStartTime = 0;

		/*
		 * 如果我们负责（重新）归档接收的文件，则启动归档程序。
		 */
		Assert(PgArchPID == 0);
		if (XLogArchivingAlways())
			PgArchPID = StartArchiver();

		/*
		 * 如果我们不打算稍后进入热备份模式，则将 RECOVERY_STARTED 视为我们已不再启动，并相应地报告状态。
		 */
		if (!EnableHotStandby)
		{
			AddToDataDirLockFile(LOCK_FILE_LINE_PM_STATUS, PM_STATUS_STANDBY);
#ifdef USE_SYSTEMD
			sd_notify(0, "READY=1");
#endif
		}

		pmState = PM_RECOVERY;
	}

	if (CheckPostmasterSignal(PMSIGNAL_BEGIN_HOT_STANDBY) &&
		pmState == PM_RECOVERY && Shutdown == NoShutdown)
	{
		ereport(LOG,
				(errmsg("database system is ready to accept read-only connections")));

		/* 报告状态 */
		AddToDataDirLockFile(LOCK_FILE_LINE_PM_STATUS, PM_STATUS_READY);
#ifdef USE_SYSTEMD
		sd_notify(0, "READY=1");
#endif

		pmState = PM_HOT_STANDBY;
		connsAllowed = true;

		/* 现在可能会调度某些工作进程启动 */
		StartWorkerNeeded = true;
	}

	/* 处理后台工作进程状态变化。 */
	if (CheckPostmasterSignal(PMSIGNAL_BACKGROUND_WORKER_CHANGE))
	{
		/* 仅当未停止时接受新的工作请求。 */
		BackgroundWorkerStateChange(pmState < PM_STOP_BACKENDS);
		StartWorkerNeeded = true;
	}

	if (StartWorkerNeeded || HaveCrashedWorker)
		fc_maybe_start_bgworkers();

	/* 如果请求，告诉 syslogger 轮换日志文件 */
	if (SysLoggerPID != 0)
	{
		if (CheckLogrotateSignal())
		{
			fc_signal_child(SysLoggerPID, SIGUSR1);
			RemoveLogrotateSignalFiles();
		}
		else if (CheckPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE))
		{
			fc_signal_child(SysLoggerPID, SIGUSR1);
		}
	}

	if (CheckPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER) &&
		Shutdown <= SmartShutdown && pmState < PM_STOP_BACKENDS)
	{
		/*
		 * 启动一次自动真空守护进程的迭代，即使名义上未启用自动真空。这是为了我们可以主动防御事务 ID 包装。
		 * 我们为主循环设置一个标志来执行此操作，而不是试图在这里执行 --- 这是因为自动真空进程本身可能发送信号，我们希望通过在当前迭代完成后尽快启动另一个迭代来处理它。
		 */
		start_autovac_launcher = true;
	}

	if (CheckPostmasterSignal(PMSIGNAL_START_AUTOVAC_WORKER) &&
		Shutdown <= SmartShutdown && pmState < PM_STOP_BACKENDS)
	{
		/* 自动真空启动器希望我们启动一个工作进程。 */
		fc_StartAutovacuumWorker();
	}

	if (CheckPostmasterSignal(PMSIGNAL_START_WALRECEIVER))
	{
		/* 启动进程希望我们启动 walreceiver 进程。 */
		/* 如果可能，立即启动，否则记住请求以便稍后处理。 */
		WalReceiverRequested = true;
		fc_MaybeStartWalReceiver();
	}

	/*
	 * 尝试推进主进程的状态机，如果子进程请求的话。
	 *
	 * 注意此操作相对于sigusr1_handler其他操作的顺序。一般来说，这应该在其他操作之后进行，以防它们对PostmasterStateMachine有需要知道的影响。
	 * 但是，我们应该在CheckPromoteSignal步骤之前进行，因为它对状态机没有任何（立即的）影响，但依赖于我们当前的状态。
	 */
	if (CheckPostmasterSignal(PMSIGNAL_ADVANCE_STATE_MACHINE))
	{
		fc_PostmasterStateMachine();
	}

	if (StartupPID != 0 &&
		(pmState == PM_STARTUP || pmState == PM_RECOVERY ||
		 pmState == PM_HOT_STANDBY) &&
		CheckPromoteSignal())
	{
		/*
		 * 告诉启动进程完成恢复。
		 *
		 * 保留提升信号文件，让启动进程处理取消操作。
		 */
		fc_signal_child(StartupPID, SIGUSR2);
	}

#ifdef WIN32
	PG_SETMASK(&UnBlockSig);
#endif

	errno = fc_save_errno;
}

/*
 * SIGTERM 在处理启动数据包时。
 *
 * 从信号处理程序中运行proc_exit()将非常不安全。
 * 然而，由于我们尚未访问共享内存，因此我们可以直接断开连接并退出，而无需运行任何atexit处理程序。
 *
 * 可能会有人想尝试发送一条消息或记录一条消息，表明我们为何断开连接。然而，这本身也是相当不安全的。
 * 此外，向尚未完成身份验证甚至尚未向我们发送启动数据包的客户端提供关于数据库状态的线索似乎不可取。
 */
static void fc_process_startup_packet_die(SIGNAL_ARGS)
{
	_exit(1);
}

/*
 * 虚拟信号处理程序
 *
 * 我们将其用于主进程中实际上不使用的信号，但在后端中使用。如果我们在主进程中对这些信号使用SIG_IGN，那么新启动的后端可能会丢失在其能重新配置信号处理之前到达的信号。(请参见tcop/postgres.c中的注释。)
 */
static void fc_dummy_handler(SIGNAL_ARGS)
{
}

/*
 * 在处理启动数据包时超时。
 * 对于process_startup_packet_die()，我们通过_exit(1)退出。
 */
static void fc_StartupPacketTimeoutHandler(void)
{
	_exit(1);
}


/*
 * 生成随机取消键。
 */
static bool fc_RandomCancelKey(int32 *fc_cancel_key)
{
	return pg_strong_random(fc_cancel_key, sizeof(int32));
}

/*
 * 计算指定类型的子进程数量（总是排除dead_end子进程）。
 */
static int fc_CountChildren(int fc_target)
{
	dlist_iter	fc_iter;
	int			fc_cnt = 0;

	dlist_foreach(fc_iter, &BackendList)
	{
		Backend    *fc_bp = dlist_container(Backend, elem, fc_iter.cur);

		if (fc_bp->dead_end)
			continue;

		/*
		 * 由于target == BACKEND_TYPE_ALL是最常见的情况，我们首先测试
		 * 它，并避免为每个子进程访问共享内存。
		 */
		if (fc_target != BACKEND_TYPE_ALL)
		{
			/*
			 * 为任何最近宣布的WAL Sender进程分配bkend_type。
			 */
			if (fc_bp->bkend_type == BACKEND_TYPE_NORMAL &&
				IsPostmasterChildWalSender(fc_bp->child_slot))
				fc_bp->bkend_type = BACKEND_TYPE_WALSND;

			if (!(fc_target & fc_bp->bkend_type))
				continue;
		}

		fc_cnt++;
	}
	return fc_cnt;
}


/*
 * StartChildProcess -- 为主进程启动辅助进程
 *
 * "type" 决定将启动哪种类型的子进程。所有子进程类型最初都进入AuxiliaryProcessMain，将处理公共设置。
 *
 * StartChildProcess的返回值是子进程的PID，如果启动子进程失败则返回0。
 */
static pid_t fc_StartChildProcess(AuxProcType fc_type)
{
	pid_t		fc_pid;

#ifdef EXEC_BACKEND
	{
		char	   *fc_av[10];
		int			fc_ac = 0;
		char		fc_typebuf[32];

		/*
		 * 为子进程设置命令行参数
		 */
		fc_av[fc_ac++] = "postgres";
		fc_av[fc_ac++] = "--forkaux";
		fc_av[fc_ac++] = NULL;		/* 由 postmaster_forkexec 填充 */

		snprintf(fc_typebuf, sizeof(fc_typebuf), "%d", fc_type);
		fc_av[fc_ac++] = fc_typebuf;

		fc_av[fc_ac] = NULL;
		Assert(fc_ac < lengthof(fc_av));

		fc_pid = postmaster_forkexec(fc_ac, fc_av);
	}
#else							/* !EXEC_BACKEND */
	fc_pid = fork_process();

	if (fc_pid == 0)				/* child */
	{
		InitPostmasterChild();

		/* 关闭 postmaster 的套接字 */
		ClosePostmasterPorts(false);

		/* 释放主进程的工作内存上下文 */
		MemoryContextSwitchTo(TopMemoryContext);
		MemoryContextDelete(PostmasterContext);
		PostmasterContext = NULL;

		AuxiliaryProcessMain(fc_type); /* 不会返回 */
	}
#endif							/*
 * PGSharedMemoryDetach
 *
 * 从共享内存段分离，如果仍然附加。这不是旨在由最初创建该段的进程显式调用（它将注册 on_shmem_exit 回调来做到这一点）。相反，这是为那些继承了附加并希望摆脱它的子进程而设。
 *
 * UsedShmemSegID 和 UsedShmemSegAddr 也是该例程的隐式参数，还有 AnonymousShmem 和 AnonymousShmemSize。
 */

	if (fc_pid < 0)
	{
		/* 在父进程中，fork 失败 */
		int			fc_save_errno = errno;

		errno = fc_save_errno;
		switch (fc_type)
		{
			case StartupProcess:
				ereport(LOG,
						(errmsg("could not fork startup process: %m")));
				break;
			case ArchiverProcess:
				ereport(LOG,
						(errmsg("could not fork archiver process: %m")));
				break;
			case BgWriterProcess:
				ereport(LOG,
						(errmsg("could not fork background writer process: %m")));
				break;
			case CheckpointerProcess:
				ereport(LOG,
						(errmsg("could not fork checkpointer process: %m")));
				break;
			case WalWriterProcess:
				ereport(LOG,
						(errmsg("could not fork WAL writer process: %m")));
				break;
			case WalReceiverProcess:
				ereport(LOG,
						(errmsg("could not fork WAL receiver process: %m")));
				break;
			default:
				ereport(LOG,
						(errmsg("could not fork process: %m")));
				break;
		}

		/*
		 * 启动时fork失败是致命的，但如果启动其他子进程类型失败没有必要立即中止。
		 */
		if (fc_type == StartupProcess)
			fc_ExitPostmaster(1);
		return 0;
	}

	/*
	 * 在父进程中，成功的fork
	 */
	return fc_pid;
}

/*
 * StartAutovacuumWorker
 *		启动一个自动清理工作进程。
 *
 * 此函数在此处是因为它将结果PID输入主进程的私有后端列表中。
 *
 * 注意 -- 此代码非常粗略地匹配BackendStartup。
 */
static void fc_StartAutovacuumWorker(void)
{
	Backend    *fc_bn;

	/*
	 * 如果不具备运行进程的条件，则不要尝试，但将其像处理fork失败一样处理。这通常不会发生，因为信号只应由autovacuum启动器在可以执行时发送，但我们必须检查以避免在数据库状态更改期间发生竞争条件问题。
	 */
	if (fc_canAcceptConnections(BACKEND_TYPE_AUTOVAC) == CAC_OK)
	{
		/*
		 * 计算将分配给此会话的取消键。我们可能不需要自动清理工作进程的取消键，但最好在字段中有一些随机内容，以防不友好的人向他们发送取消请求。
		 */
		if (!fc_RandomCancelKey(&MyCancelKey))
		{
			ereport(LOG,
					(errcode(ERRCODE_INTERNAL_ERROR),
					 errmsg("could not generate random cancel key")));
			return;
		}

		fc_bn = (Backend *) malloc(sizeof(Backend));
		if (fc_bn)
		{
			fc_bn->cancel_key = MyCancelKey;

			/* 自动清理工人不是dead_end并且需要一个子槽 */
			fc_bn->dead_end = false;
			fc_bn->child_slot = MyPMChildSlot = AssignPostmasterChildSlot();
			fc_bn->bgworker_notify = false;

			fc_bn->pid = StartAutoVacWorker();
			if (fc_bn->pid > 0)
			{
				fc_bn->bkend_type = BACKEND_TYPE_AUTOVAC;
				dlist_push_head(&BackendList, &fc_bn->elem);
#ifdef EXEC_BACKEND
				fc_ShmemBackendArrayAdd(fc_bn);
#endif
				/* all OK */
				return;
			}

			/*
			 * fork失败，继续报告 -- 实际错误消息由StartAutoVacWorker记录
			 */
			(void) ReleasePostmasterChildSlot(fc_bn->child_slot);
			free(fc_bn);
		}
		else
			ereport(LOG,
					(errcode(ERRCODE_OUT_OF_MEMORY),
					 errmsg("out of memory")));
	}

	/*
	 * 向启动器报告失败，如果它正在运行。（如果它没有，我们可能甚至没有连接到共享内存，因此不要尝试调用AutoVacWorkerFailed。）注意我们还需要向其发送信号，以便它响应该条件，但我们在这里不这样做，而是等待ServerLoop来完成。这样我们可以避免在autovac启动器和主进程之间的快速顺序中进行乒乓信号。
	 */
	if (AutoVacPID != 0)
	{
		AutoVacWorkerFailed();
		avlauncher_needs_signal = true;
	}
}

/*
 * MaybeStartWalReceiver
 *		如果不在运行且我们的状态允许，则启动WAL接收进程。
 *
 * 注意：如果WalReceiverPID已经非零，则看起来我们应该清除WalReceiverRequested。然而，如果walreceiver终止而启动进程立即请求一个新的，这将导致竞争条件：在收回已终止的walreceiver进程之前，获取请求信号非常有可能。更好的做法是冒险启动一个额外的walreceiver，而不是错过启动我们需要的一个。（walreceiver代码有逻辑来识别如果不需要就应该退出。）
 */
static void fc_MaybeStartWalReceiver(void)
{
	if (WalReceiverPID == 0 &&
		(pmState == PM_STARTUP || pmState == PM_RECOVERY ||
		 pmState == PM_HOT_STANDBY) &&
		Shutdown <= SmartShutdown)
	{
		WalReceiverPID = StartWalReceiver();
		if (WalReceiverPID != 0)
			WalReceiverRequested = false;
		/* 否则保持标志设置，这样我们稍后会再尝试 */
	}
}


/*
 * 创建opts文件
 */
static bool fc_CreateOptsFile(int fc_argc, char *fc_argv[], char *fc_fullprogname)
{
	FILE	   *fc_fp;
	int			fc_i;

#define OPTS_FILE	"postmaster.opts"

	if ((fc_fp = fopen(OPTS_FILE, "w")) == NULL)
	{
		ereport(LOG,
				(errcode_for_file_access(),
				 errmsg("could not create file \"%s\": %m", OPTS_FILE)));
		return false;
	}

	fprintf(fc_fp, "%s", fc_fullprogname);
#ifdef FDD
	for (fc_i = 1; fc_i < fc_argc; fc_i++)
	{
		if(strcmp(fc_argv[fc_i], "-K") == 0 ||strcmp(fc_argv[fc_i], "-M") == 0)
		{
			fc_i++;
			fc_argv[fc_i] = NULL;
			continue;
		}
		fprintf(fc_fp, " \"%s\"", fc_argv[fc_i]);
	}
#else
	for (fc_i = 1; fc_i < fc_argc; fc_i++)
		fprintf(fc_fp, " \"%s\"", fc_argv[fc_i]);
#endif
	fputs("\n", fc_fp);

	if (fclose(fc_fp))
	{
		ereport(LOG,
				(errcode_for_file_access(),
				 errmsg("could not write file \"%s\": %m", OPTS_FILE)));
		return false;
	}

	return true;
}



/*
 * MaxLivePostmasterChildren
 *
 * 这报告了每个子进程数组中所需条目的数量
 * （PMChildFlags 数组，如果 EXEC_BACKEND 则为 ShmemBackendArray）。
 * 这些数组包括常规后端、自动真空工作进程、WAL 发送器
 * 和背景工作进程，但不包括特殊子进程或死端子进程。
 * 这允许数组具有固定的最大大小，即与 canAcceptConnections() 强制的
 * 同样的过多子进程限制。只要它大于 MaxBackends，确切的值
 * 并不是太关键。
 */
int MaxLivePostmasterChildren(void)
{
	return 2 * (MaxConnections + autovacuum_max_workers + 1 +
				max_wal_senders + max_worker_processes);
}

/*
 * 将背景工作进程连接到数据库。
 */
void BackgroundWorkerInitializeConnection(const char *fc_dbname, const char *fc_username, uint32 fc_flags)
{
	BackgroundWorker *fc_worker = MyBgworkerEntry;

	/* XXX 这是正确的错误代码吗？ */
	if (!(fc_worker->bgw_flags & BGWORKER_BACKEND_DATABASE_CONNECTION))
		ereport(FATAL,
				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
				 errmsg("database connection requirement not indicated during registration")));

	InitPostgres(fc_dbname, InvalidOid,	/* 要连接的数据库 */
				 fc_username, InvalidOid,	/* 以哪个角色连接 */
				 false,			/* 永远不遵循 session_preload_libraries */
				 (fc_flags & BGWORKER_BYPASS_ALLOWCONN) != 0,	/* 忽略 datallowconn？ */
				 NULL);			/* 没有 out_dbname */

	/* 这最好不要已经退出 "init" 模式 */
	if (!IsInitProcessingMode())
		ereport(ERROR,
				(errmsg("invalid processing mode in background worker")));
	SetProcessingMode(NormalProcessing);
}

/*
 * 使用 OID 连接后台工作进程到数据库。
 */
void BackgroundWorkerInitializeConnectionByOid(Oid fc_dboid, Oid fc_useroid, uint32 fc_flags)
{
	BackgroundWorker *fc_worker = MyBgworkerEntry;

	/* XXX 这是正确的错误代码吗？ */
	if (!(fc_worker->bgw_flags & BGWORKER_BACKEND_DATABASE_CONNECTION))
		ereport(FATAL,
				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
				 errmsg("database connection requirement not indicated during registration")));

	InitPostgres(NULL, fc_dboid,	/* 要连接的数据库 */
				 NULL, fc_useroid, /* 以哪个角色连接 */
				 false,			/* 永远不遵循 session_preload_libraries */
				 (fc_flags & BGWORKER_BYPASS_ALLOWCONN) != 0,	/* 忽略 datallowconn？ */
				 NULL);			/* 没有 out_dbname */

	/* 这最好不要已经退出 "init" 模式 */
	if (!IsInitProcessingMode())
		ereport(ERROR,
				(errmsg("invalid processing mode in background worker")));
	SetProcessingMode(NormalProcessing);
}

/*
 * 在后台工作进程中阻塞/解除阻塞信号
 */
void BackgroundWorkerBlockSignals(void)
{
	PG_SETMASK(&BlockSig);
}

void BackgroundWorkerUnblockSignals(void)
{
	PG_SETMASK(&UnBlockSig);
}

#ifdef EXEC_BACKEND
static pid_t fc_bgworker_forkexec(int fc_shmem_slot)
{
	char	   *fc_av[10];
	int			fc_ac = 0;
	char		fc_forkav[MAXPGPATH];

	snprintf(fc_forkav, MAXPGPATH, "--forkbgworker=%d", fc_shmem_slot);

	fc_av[fc_ac++] = "postgres";
	fc_av[fc_ac++] = fc_forkav;
	fc_av[fc_ac++] = NULL;			/* 由 postmaster_forkexec 填充 */
	fc_av[fc_ac] = NULL;

	Assert(fc_ac < lengthof(fc_av));

	return postmaster_forkexec(fc_ac, fc_av);
}
#endif

/*
 * 启动一个新的 bgworker。
 * 启动时间条件必须已被检查。
 *
 * 成功时返回 true，失败时返回 false。
 * 在任何情况下，适当地更新 RegisteredBgWorker 的状态。
 *
 * 这段代码 heavily 基于 autovacuum.c，请参见。
 */
static bool fc_do_start_bgworker(RegisteredBgWorker *fc_rw)
{
	pid_t		fc_worker_pid;

	Assert(fc_rw->rw_pid == 0);

	/*
	 * 分配并分配 Backend 元素。请注意，我们必须在
	 * fork 之前这样做，以便我们可以干净地处理失败（内存不足或子进程
	 * 插槽）。
	 *
	 * 将失败视为工作进程崩溃。这样，postmaster 会等待一段时间
	 * 然后再尝试重新启动它；如果我们立即再试，很可能会发现
	 * 我们面临相同的资源耗尽情况。
	 */
	if (!fc_assign_backendlist_entry(fc_rw))
	{
		fc_rw->rw_crashed_at = GetCurrentTimestamp();
		return false;
	}

	ereport(DEBUG1,
			(errmsg_internal("starting background worker process \"%s\"",
							 fc_rw->rw_worker.bgw_name)));

#ifdef EXEC_BACKEND
	switch ((fc_worker_pid = fc_bgworker_forkexec(fc_rw->rw_shmem_slot)))
#else
	switch ((fc_worker_pid = fork_process()))
#endif
	{
		case -1:
			/* 在 postmaster 中，fork 失败 ... */
			ereport(LOG,
					(errmsg("could not fork worker process: %m")));
			/* 撤销 assign_backendlist_entry 所做的操作 */
			ReleasePostmasterChildSlot(fc_rw->rw_child_slot);
			fc_rw->rw_child_slot = 0;
			free(fc_rw->rw_backend);
			fc_rw->rw_backend = NULL;
			/* 将条目标记为崩溃，以便稍后再尝试 */
			fc_rw->rw_crashed_at = GetCurrentTimestamp();
			break;

#ifndef EXEC_BACKEND
		case 0:
			/* 在 postmaster 子进程中... */
			InitPostmasterChild();

			/* 关闭 postmaster 的套接字 */
			ClosePostmasterPorts(false);

			/*
			 * 在销毁 PostmasterContext 之前，将此 bgworker 的
			 * 数据保存到它可以找到的地方。
			 */
			MyBgworkerEntry = (BackgroundWorker *)
				MemoryContextAlloc(TopMemoryContext, sizeof(BackgroundWorker));
			memcpy(MyBgworkerEntry, &fc_rw->rw_worker, sizeof(BackgroundWorker));

			/* 释放主进程的工作内存上下文 */
			MemoryContextSwitchTo(TopMemoryContext);
			MemoryContextDelete(PostmasterContext);
			PostmasterContext = NULL;

			StartBackgroundWorker();

			exit(1);			/* 不应该到达这里 */
			break;
#endif
		default:
			/* 在 postmaster 中，fork 成功 ... */
			fc_rw->rw_pid = fc_worker_pid;
			fc_rw->rw_backend->pid = fc_rw->rw_pid;
			ReportBackgroundWorkerPID(fc_rw);
			/* 将新工作进程添加到后台进程列表中 */
			dlist_push_head(&BackendList, &fc_rw->rw_backend->elem);
#ifdef EXEC_BACKEND
			fc_ShmemBackendArrayAdd(fc_rw->rw_backend);
#endif
			return true;
	}

	return false;
}

/*
 * 当前的 postmaster 状态是否需要使用指定的 start_time 启动工作进程？
 */
static bool fc_bgworker_should_start_now(BgWorkerStartTime fc_start_time)
{
	switch (pmState)
	{
		case PM_NO_CHILDREN:
		case PM_WAIT_DEAD_END:
		case PM_SHUTDOWN_2:
		case PM_SHUTDOWN:
		case PM_WAIT_BACKENDS:
		case PM_STOP_BACKENDS:
			break;

		case PM_RUN:
			if (fc_start_time == BgWorkerStart_RecoveryFinished)
				return true;
			/* fall through */

		case PM_HOT_STANDBY:
			if (fc_start_time == BgWorkerStart_ConsistentState)
				return true;
			/* fall through */

		case PM_RECOVERY:
		case PM_STARTUP:
		case PM_INIT:
			if (fc_start_time == BgWorkerStart_PostmasterStart)
				return true;
			/* fall through */
	}

	return false;
}

/*
 * 为已连接的后台工作进程分配 Backend 结构，但暂时不要
 * 将其添加到后台进程列表中。
 *
 * 失败时，返回 false 而不更改任何工作进程状态。
 *
 * Backend 中的一些信息被复制到传入的 rw 中。
 */
static bool fc_assign_backendlist_entry(RegisteredBgWorker *fc_rw)
{
	Backend    *fc_bn;

	/*
	 * 检查数据库状态是否允许另一个连接。目前唯一可能的失败是
	 * CAC_TOOMANY，因此我们只根据此记录一条错误消息，而不是精确检查
	 * 错误代码。
	 */
	if (fc_canAcceptConnections(BACKEND_TYPE_BGWORKER) != CAC_OK)
	{
		ereport(LOG,
				(errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
				 errmsg("no slot available for new worker process")));
		return false;
	}

	/*
	 * 计算分配给此会话的取消键。我们可能不需要后台工作进程的取消键，
	 * 但最好在字段中有一些随机的内容，以防止不友好的人向他们发送取消。
	 */
	if (!fc_RandomCancelKey(&MyCancelKey))
	{
		ereport(LOG,
				(errcode(ERRCODE_INTERNAL_ERROR),
				 errmsg("could not generate random cancel key")));
		return false;
	}

	fc_bn = malloc(sizeof(Backend));
	if (fc_bn == NULL)
	{
		ereport(LOG,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("out of memory")));
		return false;
	}

	fc_bn->cancel_key = MyCancelKey;
	fc_bn->child_slot = MyPMChildSlot = AssignPostmasterChildSlot();
	fc_bn->bkend_type = BACKEND_TYPE_BGWORKER;
	fc_bn->dead_end = false;
	fc_bn->bgworker_notify = false;

	fc_rw->rw_backend = fc_bn;
	fc_rw->rw_child_slot = fc_bn->child_slot;

	return true;
}

/*
 * 如果时间合适，启动后台工作进程。
 *
 * 作为副作用，bgworker 控制变量根据是否需要启动更多工作进程而被设置或重置。
 *
 * 每次调用启动的工作进程的数量是有限的，以避免在许多请求待处理时
 * 消耗 postmaster 的注意力。只要 StartWorkerNeeded 为 true，ServerLoop
 * 就不会阻塞，并将在处理完其他问题后再次调用此函数。
 */
static void fc_maybe_start_bgworkers(void)
{
#define MAX_BGWORKERS_TO_LAUNCH 100
	int			fc_num_launched = 0;
	TimestampTz fc_now = 0;
	slist_mutable_iter fc_iter;

	/*
	 * 在崩溃恢复期间，我们不需要在恢复状态转换之前被调用。
	 */
	if (FatalError)
	{
		StartWorkerNeeded = false;
		HaveCrashedWorker = false;
		return;
	}

	/* 除非我们在下面找到原因，否则不需要再次被调用 */
	StartWorkerNeeded = false;
	HaveCrashedWorker = false;

	slist_foreach_modify(fc_iter, &BackgroundWorkerList)
	{
		RegisteredBgWorker *fc_rw;

		fc_rw = slist_container(RegisteredBgWorker, rw_lnode, fc_iter.cur);

		/* 如果已在运行则忽略 */
		if (fc_rw->rw_pid != 0)
			continue;

		/* 如果标记为死亡，进行清理并从列表中删除 */
		if (fc_rw->rw_terminate)
		{
			ForgetBackgroundWorker(&fc_iter);
			continue;
		}

		/*
		 * 如果此工作进程之前崩溃过，也许需要重新启动它
		 * （除非在注册时指定不想被重新启动）。检查距离上次崩溃经历多久。
		 * 如果上次崩溃发生得太近，立刻不要启动；让它在过去足够的时间后
		 * 再被重新启动。
		 */
		if (fc_rw->rw_crashed_at != 0)
		{
			if (fc_rw->rw_worker.bgw_restart_time == BGW_NEVER_RESTART)
			{
				int			fc_notify_pid;

				fc_notify_pid = fc_rw->rw_worker.bgw_notify_pid;

				ForgetBackgroundWorker(&fc_iter);

				/* 报告工作进程现在已经消失。 */
				if (fc_notify_pid != 0)
					kill(fc_notify_pid, SIGUSR1);

				continue;
			}

			/* 仅在需要时读取系统时间 */
			if (fc_now == 0)
				fc_now = GetCurrentTimestamp();

			if (!TimestampDifferenceExceeds(fc_rw->rw_crashed_at, fc_now,
											fc_rw->rw_worker.bgw_restart_time * 1000))
			{
				/* 设置标志以记住稍后有工作进程要启动 */
				HaveCrashedWorker = true;
				continue;
			}
		}

		if (fc_bgworker_should_start_now(fc_rw->rw_worker.bgw_start_time))
		{
			/* 在尝试启动工作进程之前重置崩溃时间 */
			fc_rw->rw_crashed_at = 0;

			/*
			 * 尝试启动工作进程。
			 *
			 * 在失败时，暂时放弃处理工作进程，但设置 
			 * StartWorkerNeeded，以便在下一次的 ServerLoop 中我们可以回到这里再次尝试。 
			 * （我们不想等待，因为可能还有其他准备好的工作进程。） 
			 * 我们可以同样设置 HaveCrashedWorker，因为这个工作进程现在被标记为崩溃，
			 * 但因为此函数的下一次运行会处理这个，所以没有必要。
			 */
			if (!fc_do_start_bgworker(fc_rw))
			{
				StartWorkerNeeded = true;
				return;
			}

			/*
			 * 如果我们已启动了允许的工作进程数量，则退出，
			 * 但让 ServerLoop 再次调用我们以查找其他准备运行的
			 * 工作进程。 可能没有，但我们在下次运行时会发现。
			 */
			if (++fc_num_launched >= MAX_BGWORKERS_TO_LAUNCH)
			{
				StartWorkerNeeded = true;
				return;
			}
		}
	}
}

/*
 * 当后台进程请求通知工作状态变化时，我们在其后台进程条目中
 * 设置一个标志。后台工作进程机制需要知道此类后台进程何时退出。
 */
bool PostmasterMarkPIDForWorkerNotify(int fc_pid)
{
	dlist_iter	fc_iter;
	Backend    *fc_bp;

	dlist_foreach(fc_iter, &BackendList)
	{
		fc_bp = dlist_container(Backend, elem, fc_iter.cur);
		if (fc_bp->pid == fc_pid)
		{
			fc_bp->bgworker_notify = true;
			return true;
		}
	}
	return false;
}

#ifdef EXEC_BACKEND

/*
 * 以下内容需要在 save/restore_backend_variables
 * 函数中使用。它们在各自的模块中标记为 NON_EXEC_STATIC。
 */
extern slock_t *ShmemLock;
extern slock_t *ProcStructLock;
extern PGPROC *AuxiliaryProcs;
extern PMSignalData *PMSignalState;
extern pg_time_t first_syslogger_file_time;

#ifndef WIN32
#define write_inheritable_socket(dest, src, childpid) ((*(dest) = (src)), true)
#define read_inheritable_socket(dest, src) (*(dest) = *(src))
#else
static bool write_duplicated_handle(HANDLE *dest, HANDLE src, HANDLE child);
static bool write_inheritable_socket(InheritableSocket *dest, SOCKET src,
									 pid_t childPid);
static void read_inheritable_socket(SOCKET *dest, InheritableSocket *src);
#endif


/* 将关键的后端变量保存到 BackendParameters 结构中 */
#ifndef WIN32
static bool fc_save_backend_variables(BackendParameters *fc_param, Port *fc_port)
#else
static bool save_backend_variables(BackendParameters *param, Port *port,
					   HANDLE childProcess, pid_t childPid)
#endif
{
	memcpy(&fc_param->port, fc_port, sizeof(Port));
	if (!write_inheritable_socket(&fc_param->portsocket, fc_port->sock, childPid))
		return false;

	strlcpy(fc_param->DataDir, DataDir, MAXPGPATH);

	memcpy(&fc_param->ListenSocket, &ListenSocket, sizeof(ListenSocket));

	fc_param->MyCancelKey = MyCancelKey;
	fc_param->MyPMChildSlot = MyPMChildSlot;

#ifdef WIN32
	fc_param->ShmemProtectiveRegion = ShmemProtectiveRegion;
#endif
	fc_param->UsedShmemSegID = UsedShmemSegID;
	fc_param->UsedShmemSegAddr = UsedShmemSegAddr;

	fc_param->ShmemLock = ShmemLock;
	fc_param->ShmemVariableCache = ShmemVariableCache;
	fc_param->ShmemBackendArray = ShmemBackendArray;

#ifndef HAVE_SPINLOCKS
	fc_param->SpinlockSemaArray = SpinlockSemaArray;
#endif
	fc_param->NamedLWLockTrancheRequests = NamedLWLockTrancheRequests;
	fc_param->NamedLWLockTrancheArray = NamedLWLockTrancheArray;
	fc_param->MainLWLockArray = MainLWLockArray;
	fc_param->ProcStructLock = ProcStructLock;
	fc_param->ProcGlobal = ProcGlobal;
	fc_param->AuxiliaryProcs = AuxiliaryProcs;
	fc_param->PreparedXactProcs = PreparedXactProcs;
	fc_param->PMSignalState = PMSignalState;

	fc_param->PostmasterPid = PostmasterPid;
	fc_param->PgStartTime = PgStartTime;
	fc_param->PgReloadTime = PgReloadTime;
	fc_param->first_syslogger_file_time = first_syslogger_file_time;

	fc_param->redirection_done = redirection_done;
	fc_param->IsBinaryUpgrade = IsBinaryUpgrade;
	fc_param->query_id_enabled = query_id_enabled;
	fc_param->max_safe_fds = max_safe_fds;

	fc_param->MaxBackends = MaxBackends;

#ifdef WIN32
	param->PostmasterHandle = PostmasterHandle;
	if (!write_duplicated_handle(&param->initial_signal_pipe,
								 pgwin32_create_signal_listener(childPid),
								 childProcess))
		return false;
#else
	memcpy(&fc_param->postmaster_alive_fds, &postmaster_alive_fds,
		   sizeof(postmaster_alive_fds));
#endif

	memcpy(&fc_param->syslogPipe, &syslogPipe, sizeof(syslogPipe));

	strlcpy(fc_param->my_exec_path, my_exec_path, MAXPGPATH);

	strlcpy(fc_param->pkglib_path, pkglib_path, MAXPGPATH);

	return true;
}


#ifdef WIN32
/*
 * 复制一个句柄以便在子进程中使用，并将子进程实例的句柄写入参数文件。
 */
static bool write_duplicated_handle(HANDLE *dest, HANDLE src, HANDLE childProcess)
{
	HANDLE		hChild = INVALID_HANDLE_VALUE;

	if (!DuplicateHandle(GetCurrentProcess(),
						 src,
						 childProcess,
						 &hChild,
						 0,
						 TRUE,
						 DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS))
	{
		ereport(LOG,
				(errmsg_internal("could not duplicate handle to be written to backend parameter file: error code %lu",
								 GetLastError())));
		return false;
	}

	*dest = hChild;
	return true;
}

/*
 * 复制一个套接字以便在子进程中使用，并将结果结构写入参数文件。
 * 这是必需的，因为许多非常常见的 LSP（分层服务提供商）在 Windows 上（杀毒软件、防火墙、下载管理器等）会破坏直接的套接字继承。
 */
static bool write_inheritable_socket(InheritableSocket *dest, SOCKET src, pid_t childpid)
{
	dest->origsocket = src;
	if (src != 0 && src != PGINVALID_SOCKET)
	{
		/* 实际的套接字 */
		if (WSADuplicateSocket(src, childpid, &dest->wsainfo) != 0)
		{
			ereport(LOG,
					(errmsg("could not duplicate socket %d for use in backend: error code %d",
							(int) src, WSAGetLastError())));
			return false;
		}
	}
	return true;
}

/*
 * 从文件中读取重复的套接字结构，并获取套接字描述符。
 */
static void read_inheritable_socket(SOCKET *dest, InheritableSocket *src)
{
	SOCKET		s;

	if (src->origsocket == PGINVALID_SOCKET || src->origsocket == 0)
	{
		/* 不是一个真实的套接字！ */
		*dest = src->origsocket;
	}
	else
	{
		/* 实际的套接字，因此从结构中创建 */
		s = WSASocket(FROM_PROTOCOL_INFO,
					  FROM_PROTOCOL_INFO,
					  FROM_PROTOCOL_INFO,
					  &src->wsainfo,
					  0,
					  0);
		if (s == INVALID_SOCKET)
		{
			write_stderr("could not create inherited socket: error code %d\n",
						 WSAGetLastError());
			exit(1);
		}
		*dest = s;

		/*
		 * 为确保我们不会获得对同一套接字的两个引用，关闭
		 * 原始的套接字。（当继承实际
		 * 工作时，这将发生..）
 */
		closesocket(src->origsocket);
	}
}
#endif

static void fc_read_backend_variables(char *fc_id, Port *fc_port)
{
	BackendParameters fc_param;

#ifndef WIN32
	/* 非 win32 实现从文件中读取 */
	FILE	   *fc_fp;

	/* 打开文件 */
	fc_fp = AllocateFile(fc_id, PG_BINARY_R);
	if (!fc_fp)
	{
		write_stderr("could not open backend variables file \"%s\": %s\n",
					 fc_id, strerror(errno));
		exit(1);
	}

	if (fread(&fc_param, sizeof(fc_param), 1, fc_fp) != 1)
	{
		write_stderr("could not read from backend variables file \"%s\": %s\n",
					 fc_id, strerror(errno));
		exit(1);
	}

	/* 释放文件 */
	FreeFile(fc_fp);
	if (unlink(fc_id) != 0)
	{
		write_stderr("could not remove file \"%s\": %s\n",
					 fc_id, strerror(errno));
		exit(1);
	}
#else
	/* Win32 版本使用映射文件 */
	HANDLE		paramHandle;
	BackendParameters *paramp;

#ifdef _WIN64
	paramHandle = (HANDLE) _atoi64(id);
#else
	paramHandle = (HANDLE) atol(id);
#endif
	paramp = MapViewOfFile(paramHandle, FILE_MAP_READ, 0, 0, 0);
	if (!paramp)
	{
		write_stderr("could not map view of backend variables: error code %lu\n",
					 GetLastError());
		exit(1);
	}

	memcpy(&param, paramp, sizeof(BackendParameters));

	if (!UnmapViewOfFile(paramp))
	{
		write_stderr("could not unmap view of backend variables: error code %lu\n",
					 GetLastError());
		exit(1);
	}

	if (!CloseHandle(paramHandle))
	{
		write_stderr("could not close handle to backend parameter variables: error code %lu\n",
					 GetLastError());
		exit(1);
	}
#endif

	fc_restore_backend_variables(&fc_param, fc_port);
}

/* 从 BackendParameters 结构中恢复关键的后端变量 */
static void fc_restore_backend_variables(BackendParameters *fc_param, Port *fc_port)
{
	memcpy(fc_port, &fc_param->port, sizeof(Port));
	read_inheritable_socket(&fc_port->sock, &fc_param->portsocket);

	SetDataDir(fc_param->DataDir);

	memcpy(&ListenSocket, &fc_param->ListenSocket, sizeof(ListenSocket));

	MyCancelKey = fc_param->MyCancelKey;
	MyPMChildSlot = fc_param->MyPMChildSlot;

#ifdef WIN32
	ShmemProtectiveRegion = param->ShmemProtectiveRegion;
#endif
	UsedShmemSegID = fc_param->UsedShmemSegID;
	UsedShmemSegAddr = fc_param->UsedShmemSegAddr;

	ShmemLock = fc_param->ShmemLock;
	ShmemVariableCache = fc_param->ShmemVariableCache;
	ShmemBackendArray = fc_param->ShmemBackendArray;

#ifndef HAVE_SPINLOCKS
	SpinlockSemaArray = fc_param->SpinlockSemaArray;
#endif
	NamedLWLockTrancheRequests = fc_param->NamedLWLockTrancheRequests;
	NamedLWLockTrancheArray = fc_param->NamedLWLockTrancheArray;
	MainLWLockArray = fc_param->MainLWLockArray;
	ProcStructLock = fc_param->ProcStructLock;
	ProcGlobal = fc_param->ProcGlobal;
	AuxiliaryProcs = fc_param->AuxiliaryProcs;
	PreparedXactProcs = fc_param->PreparedXactProcs;
	PMSignalState = fc_param->PMSignalState;

	PostmasterPid = fc_param->PostmasterPid;
	PgStartTime = fc_param->PgStartTime;
	PgReloadTime = fc_param->PgReloadTime;
	first_syslogger_file_time = fc_param->first_syslogger_file_time;

	redirection_done = fc_param->redirection_done;
	IsBinaryUpgrade = fc_param->IsBinaryUpgrade;
	query_id_enabled = fc_param->query_id_enabled;
	max_safe_fds = fc_param->max_safe_fds;

	MaxBackends = fc_param->MaxBackends;

#ifdef WIN32
	PostmasterHandle = param->PostmasterHandle;
	pgwin32_initial_signal_pipe = param->initial_signal_pipe;
#else
	memcpy(&postmaster_alive_fds, &fc_param->postmaster_alive_fds,
		   sizeof(postmaster_alive_fds));
#endif

	memcpy(&syslogPipe, &fc_param->syslogPipe, sizeof(syslogPipe));

	strlcpy(my_exec_path, fc_param->my_exec_path, MAXPGPATH);

	strlcpy(pkglib_path, fc_param->pkglib_path, MAXPGPATH);

	/*
	 * 我们需要恢复 fd.c 中外部打开的 FD 的计数；为了避免
	 * 混淆，请务必在恢复 max_safe_fds 后进行此操作。（注意：
	 * BackendInitialize 将为 port->sock 处理此问题。）
	 */
#ifndef WIN32
	if (postmaster_alive_fds[0] >= 0)
		ReserveExternalFD();
	if (postmaster_alive_fds[1] >= 0)
		ReserveExternalFD();
#endif
}


Size ShmemBackendArraySize(void)
{
	return mul_size(MaxLivePostmasterChildren(), sizeof(Backend));
}

void ShmemBackendArrayAllocation(void)
{
	Size		fc_size = ShmemBackendArraySize();

	ShmemBackendArray = (Backend *) ShmemAlloc(fc_size);
	/* 标记所有槽为可用 */
	memset(ShmemBackendArray, 0, fc_size);
}

static void fc_ShmemBackendArrayAdd(Backend *fc_bn)
{
	/* 对应于我的 PMChildSlot 的数组槽应为空 */
	int			fc_i = fc_bn->child_slot - 1;

	Assert(ShmemBackendArray[fc_i].pid == 0);
	ShmemBackendArray[fc_i] = *fc_bn;
}

static void fc_ShmemBackendArrayRemove(Backend *fc_bn)
{
	int			fc_i = fc_bn->child_slot - 1;

	Assert(ShmemBackendArray[fc_i].pid == fc_bn->pid);
	/* 标记该槽为空 */
	ShmemBackendArray[fc_i].pid = 0;
}
#endif							/*
 * PGSharedMemoryDetach
 *
 * 从共享内存段分离，如果仍然附加。这不是旨在由最初创建该段的进程显式调用（它将注册 on_shmem_exit 回调来做到这一点）。相反，这是为那些继承了附加并希望摆脱它的子进程而设。
 *
 * UsedShmemSegID 和 UsedShmemSegAddr 也是该例程的隐式参数，还有 AnonymousShmem 和 AnonymousShmemSize。
 */


#ifdef WIN32

/*
 * Windows 的 waitpid() 子集实现。我们假设 pid 为 -1
 * （即检查所有子进程），选项为 WNOHANG（不等待）。
 */
static pid_t waitpid(pid_t pid, int *exitstatus, int options)
{
	win32_deadchild_waitinfo *childinfo;
	DWORD		exitcode;
	DWORD		dwd;
	ULONG_PTR	key;
	OVERLAPPED *ovl;

	/* 尝试从队列中消耗一个 win32_deadchild_waitinfo。 */
	if (!GetQueuedCompletionStatus(win32ChildQueue, &dwd, &key, &ovl, 0))
	{
		errno = EAGAIN;
		return -1;
	}

	childinfo = (win32_deadchild_waitinfo *) key;
	pid = childinfo->procId;

	/*
	 * 从等待中移除句柄 - 即使它设置为仅等待一次，仍然是必需的
	 */
	UnregisterWaitEx(childinfo->waitHandle, NULL);

	if (!GetExitCodeProcess(childinfo->procHandle, &exitcode))
	{
		/*
		 * 不会发生。通知用户并设置固定的退出代码。
		 */
		write_stderr("could not read exit code for process\n");
		exitcode = 255;
	}
	*exitstatus = exitcode;

	/*
	 * 关闭进程句柄。 只有在这一点之后，PID 才能由内核回收。
	 */
	CloseHandle(childinfo->procHandle);

	/*
	 * 释放在调用之前分配的结构
	 * RegisterWaitForSingleObject()
	 */
	pfree(childinfo);

	return pid;
}

/*
 * 注意！下面的代码在线程池上执行！所有操作必须
 * 是线程安全的！注意，elog() 和其他函数 *不可* 使用。
 */
static void WINAPI pgwin32_deadchild_callback(PVOID lpParameter, BOOLEAN TimerOrWaitFired)
{
	/* 不会发生，因为我们使用 INFINITE 作为超时值。 */
	if (TimerOrWaitFired)
		return;

	/*
	 * 发布 win32_deadchild_waitinfo 对象以供 waitpid() 处理。如果
	 * 失败，我们泄漏该对象，但我们也会泄漏整个进程，并且
	 * 进入无法恢复的状态，因此担心这一点并没有太大意义。
	 * 我们想要恐慌，但我们不能从这个线程中使用那个基础设施。
	 */
	if (!PostQueuedCompletionStatus(win32ChildQueue,
									0,
									(ULONG_PTR) lpParameter,
									NULL))
		write_stderr("could not post child completion status\n");

	/* 队列 SIGCHLD 信号。 */
	pg_queue_signal(SIGCHLD);
}
#endif							/* WIN32 */

/*
 * 初始化一个用于监视主进程死亡的句柄。
 *
 * 在主进程中调用一次，以便子进程可以随后
 * 监测其父进程是否已死亡。
 */
static void fc_InitPostmasterDeathWatchHandle(void)
{
#ifndef WIN32

	/*
	 * 创建一个管道。主进程保持管道的写入端打开
	 * （POSTMASTER_FD_OWN），而子进程保持读取端。子进程可以将
	 * 读取文件描述符传递给 select() 以便在主进程
	 * 死亡时唤醒，或者通过 (read() == 0) 检查主进程死亡。子进程必须
	 * 在分 fork 之后尽快关闭写入端，因为 EOF
	 * 在所有进程关闭写入 fd 之前不会在读取端被信号化。这在 ClosePostmasterPorts() 中处理。
	 */
	Assert(MyProcPid == PostmasterPid);
	if (pipe(postmaster_alive_fds) < 0)
		ereport(FATAL,
				(errcode_for_file_access(),
				 errmsg_internal("could not create pipe to monitor postmaster death: %m")));

	/* 通知 fd.c 我们已经消耗了两个用于管道的 FD。 */
	ReserveExternalFD();
	ReserveExternalFD();

	/*
	 * 设置 O_NONBLOCK 以允许通过 read()
	 * 调用测试 FD 的存在。
	 */
	if (fcntl(postmaster_alive_fds[POSTMASTER_FD_WATCH], F_SETFL, O_NONBLOCK) == -1)
		ereport(FATAL,
				(errcode_for_socket_access(),
				 errmsg_internal("could not set postmaster death monitoring pipe to nonblocking mode: %m")));
#else

	/*
	 * 在 Windows 中，我们使用进程句柄来达到相同的目的。
	 */
	if (DuplicateHandle(GetCurrentProcess(),
						GetCurrentProcess(),
						GetCurrentProcess(),
						&PostmasterHandle,
						0,
						TRUE,
						DUPLICATE_SAME_ACCESS) == 0)
		ereport(FATAL,
				(errmsg_internal("could not duplicate postmaster handle: error code %lu",
								 GetLastError())));
#endif							/* WIN32 */
}
